소개
This book is the primary reference for the Rust programming language.
Note
For known bugs and omissions in this book, see our GitHub issues. If you see a case where the compiler behavior and the text here do not agree, file an issue so we can think about which is correct.
러스트 릴리스
러스트는 6주마다 새로운 언어 릴리스를 발표합니다. 언어의 첫 번째 안정 릴리스는 Rust 1.0.0이었고, 이어서 Rust 1.1.0 등이 출시되었습니다. 도구(rustc, cargo 등) 및 문서(표준 라이브러리, 이 책 등)는 언어 릴리스와 함께 출시됩니다.
최신 Rust 버전에 해당하는 이 책의 최신 릴리스는 항상 https://doc.rust-lang.org/reference/에서 찾을 수 있습니다. 이전 버전은 “reference” 디렉토리 앞에 Rust 버전을 추가하여 찾을 수 있습니다. 예를 들어, Rust 1.49.0에 대한 참조는 https://doc.rust-lang.org/1.49.0/reference/에 있습니다.
참조 가 아닌 것
이 책은 언어 입문서가 아닙니다. 언어에 대한 기본적인 지식이 있다고 가정합니다. 이러한 배경 지식을 습득하는 데 도움이 되는 별도의 책 이 있습니다.
이 책은 또한 언어 배포판에 포함된 표준 라이브러리 에 대한 참조 역할을 하지 않습니다. 해당 라이브러리는 소스 코드에서 문서 속성을 추출하여 별도로 문서화됩니다. 언어 기능이라고 예상할 수 있는 많은 기능이 Rust에서는 라이브러리 기능이므로, 찾고 있는 것이 여기에 없을 수도 있습니다.
마찬가지로, 이 책은 일반적으로 rustc 도구 또는 Cargo의 세부 사항을 문서화하지 않습니다. rustc 에는 자체 책 이 있습니다. Cargo에는 참조 가 포함된 책 이 있습니다. 연결 과 같은 몇몇 페이지는 여전히 rustc 의 작동 방식을 설명합니다.
이 책은 또한 안정적인 Rust에서 사용할 수 있는 기능에 대한 참조 역할만 합니다. 개발 중인 불안정한 기능에 대해서는 불안정 책 을 참조하십시오.
rustc 를 포함한 Rust 컴파일러는 최적화를 수행합니다. 참조는 어떤 최적화가 허용되거나 허용되지 않는지 명시하지 않습니다. 대신, 컴파일된 프로그램을 블랙박스로 생각하십시오. 프로그램을 실행하고 입력을 제공하며 출력을 관찰함으로써만 탐색할 수 있습니다. 그렇게 발생하는 모든 것은 참조가 말하는 바를 따라야 합니다.
이 책을 사용하는 방법
이 책은 순차적으로 읽는다고 가정하지 않습니다. 각 장은 일반적으로 독립적으로 읽을 수 있지만, 언급하지만 논의하지 않는 언어의 측면에 대해서는 다른 장으로 상호 연결됩니다.
이 문서를 읽는 두 가지 주요 방법이 있습니다.
첫 번째는 특정 질문에 답하는 것입니다. 해당 질문에 답하는 장을 알고 있다면 목차에서 해당 장으로 이동할 수 있습니다. 그렇지 않으면 s 를 누르거나 상단 바의 돋보기를 클릭하여 질문과 관련된 키워드를 검색할 수 있습니다. 예를 들어, let 문에서 생성된 임시 값이 언제 삭제되는지 알고 싶다고 가정해 봅시다. 임시 값의 수명 이 표현식 장 에 정의되어 있다는 것을 이미 알고 있지 못했다면, “temporary let“을 검색하면 첫 번째 검색 결과가 해당 섹션으로 안내할 것입니다.
두 번째는 언어의 한 측면에 대한 지식을 일반적으로 향상시키는 것입니다. 이 경우, 더 알고 싶은 것을 볼 때까지 목차를 탐색하고 읽기 시작하십시오. 링크가 흥미로워 보이면 클릭하여 해당 섹션을 읽으십시오.
그렇다고 해서 이 책을 읽는 데 잘못된 방법은 없습니다. 가장 도움이 된다고 생각하는 방식으로 읽으십시오.
규약
모든 기술 서적과 마찬가지로 이 책은 정보를 표시하는 방식에 있어 특정 규칙을 따릅니다. 이러한 규칙은 여기에 문서화되어 있습니다.
-
용어를 정의하는 문장은 해당 용어를 이탤릭체 로 포함합니다. 해당 용어가 해당 장 외부에서 사용될 때마다 일반적으로 이 정의가 있는 섹션에 대한 링크입니다.
예시 용어 는 정의되는 용어의 예시입니다.
-
The main text describes the latest stable edition. Differences to previous editions are separated in edition blocks:
2018 Edition differences
Before the 2018 edition, the behavior was this. As of the 2018 edition, the behavior is that.
-
Notes that contain useful information about the state of the book or point out useful, but mostly out of scope, information are in note blocks.
Note
This is an example note.
-
Example blocks show an example that demonstrates some rule or points out some interesting aspect. Some examples may have hidden lines which can be viewed by clicking the eye icon that appears when hovering or tapping the example.
Example
This is a code example.
#![allow(unused)] fn main() { println!("hello world"); } -
언어의 불안정한 동작 또는 언어 기능의 혼란스러운 상호 작용을 보여주는 경고는 특별 경고 상자에 있습니다.
Warning
This is an example warning.
-
텍스트 내의 코드 스니펫은
<code>태그 안에 있습니다.더 긴 코드 예제는 구문 강조 표시된 상자에 있으며, 오른쪽 상단 모서리에 복사, 실행 및 숨겨진 줄 표시를 위한 컨트롤이 있습니다.
// 이것은 숨겨진 줄입니다. fn main() { println!("이것은 코드 예시입니다"); }모든 예제는 별도로 명시되지 않는 한 최신 에디션을 기준으로 작성되었습니다.
-
The grammar and lexical productions are described in the Notation chapter.
-
Rule identifiers appear before each language rule enclosed in square brackets. These identifiers provide a way to refer to and link to a specific rule in the language (e.g.). The rule identifier uses periods to separate sections from most general to most specific (destructors.scope.nesting.function-body for example). On narrow screens, the rule name will collapse to display
[*].규칙 이름을 클릭하면 해당 규칙으로 연결됩니다.
Warning
The organization of the rules is currently in flux. For the time being, these identifier names are not stable between releases, and links to these rules may fail if they are changed. We intend to stabilize these once the organization has settled so that links to the rule names will not break between releases.
-
Rules that have associated tests will include a
Testslink below them (on narrow screens, the link is[T]). Clicking the link will pop up a list of tests, which can be clicked to view the test. For example, see input.encoding.utf8.Linking rules to tests is an ongoing effort. See the Test summary chapter for an overview.
기여하기
모든 종류의 기여를 환영합니다.
You can contribute to this book by opening an issue or sending a pull request to the Rust Reference repository. If this book does not answer your question, and you think its answer is in scope of it, please do not hesitate to file an issue or ask about it in the t-lang/doc stream on Zulip. Knowing what people use this book for the most helps direct our attention to making those sections the best that they can be. And of course, if you see anything that is wrong or is non-normative but not specifically called out as such, please also file an issue.
표기법
문법
다음 표기법은 렉서 및 구문 문법 스니펫에서 사용됩니다.
| 표기법 | 예시 | 의미 |
|---|---|---|
| 대문자 | KW_IF, 정수_ 리터럴 | 렉서가 생성한 토큰 |
| 이탤릭카멜케이스 | Let문, 아이템 | 구문 생성 |
문자열 | x, while, * | 정확한 문자(들) |
| x? | pub? | 선택적 항목 |
| x* | 외부속성* | x가 0개 이상 |
| x+ | 매크로매치+ | x가 1개 이상 |
| xa..b | 16진수_숫자1..6 | a to b repetitions of x, exclusive of b |
| xa..=b | HEX_DIGIT1..=5 | a to b repetitions of x, inclusive of b |
| xn:a..=b | #n:1..=255 | a to b repetitions of x (inclusive of b), with the count bound to the name n |
| xn | #n | x repeated the number of times bound to n by a previous labeled repetition |
| Rule1 Rule2 | fn Name Parameters | Sequence of rules in order |
| ||| |-| | |u8 | u16, 블록 | 아이템| |-| | 둘 중 하나 |
| ! | !COMMENT | Matches if the expression does not follow, without consuming any input |
| [ ] | [b B] | 나열된 문자 중 하나 |
| [ - ] | [a-z] | 범위 내의 문자 중 하나 |
| ~[ ] | ~[b B] | 나열된 문자를 제외한 모든 문자 |
~문자열 | |~ , ~*/| |-| | 이 시퀀스를 제외한 모든 문자 |
| ( ) | (, 매개변수)? | 항목 그룹화 |
| ^ | b' ^ ASCII_FOR_CHAR | The rest of the sequence must match or parsing fails unconditionally (hard cut operator) |
| U+xxxx..xxxxxx | U+0060 | A single Unicode character |
| <text> | <any ASCII char except CR> | An English description of what should be matched |
| Rule suffix | IDENTIFIER_OR_KEYWORD except crate | A modification to the previous rule |
| // Comment. | // Single line comment. | A comment extending to the end of the line. |
Sequences have a higher precedence than | alternation.
The hard cut operator
The grammar uses ordered alternation: the parser tries alternatives left to right and takes the first that matches. If an alternative fails partway through a sequence, the parser normally backtracks and tries the next alternative. The cut operator (^) prevents this. Once every expression to the left of ^ in a sequence has matched, the rest of the sequence must match or parsing fails unconditionally.
Mizushima et al. introduced cut operators to parsing expression grammars. In the PEG literature, a soft cut prevents backtracking only within the immediately enclosing ordered choice — outer choices can still recover. A hard cut prevents all backtracking past the cut point; failure is definitive. The ^ used in this grammar is a hard cut.
The hard cut operator is necessary because some tokens in Rust begin with a prefix that is itself a valid token. For example, c" begins a C string literal, but c alone is a valid identifier. Without the cut, if c"\0" failed to lex as a C string literal (because null bytes are not allowed in C strings), the parser could backtrack and lex it as two tokens: the identifier c and the string literal "\0". The cut after c" prevents this — once the opening delimiter is recognized, the parser cannot go back. The same reasoning applies to byte literals, byte string literals, raw string literals, and other literals with prefixes that are themselves valid tokens.
문자열 테이블 생성
문법의 일부 규칙(특히 단항 연산자, 이항 연산자 및 키워드)은 인쇄 가능한 문자열 목록으로 단순화된 형태로 제공됩니다. 이러한 경우는 토큰 규칙에 관한 규칙의 하위 집합을 형성하며, DFA에 의해 구동되는 어휘 분석 단계가 파서에 공급하는 결과로 간주되며, 이러한 모든 문자열 테이블 항목의 논리합에 대해 작동합니다.
문법 내에서 monospace 글꼴의 문자열이 나타나면, 이는 해당 문자열 테이블 생성의 단일 멤버에 대한 암시적 참조입니다. 자세한 내용은 토큰 을 참조하십시오.
Grammar visualizations
Below each grammar block is a button to toggle the display of a syntax diagram. A square element is a non-terminal rule, and a rounded rectangle is a terminal.
어휘 구조
입력 형식
Lexer
CHAR → [U+0000-U+D7FF U+E000-U+10FFFF] // a Unicode scalar value
ASCII → [U+0000-U+007F]
NUL → U+0000
이 장에서는 소스 파일이 토큰 시퀀스로 해석되는 방법을 설명합니다.
프로그램이 파일로 구성되는 방법에 대한 설명은 크레이트 및 소스 파일 을 참조하십시오.
소스 인코딩
각 소스 파일은 UTF-8로 인코딩된 유니코드 문자 시퀀스로 해석됩니다.
파일이 유효한 UTF-8이 아니면 오류입니다.
바이트 순서 마크 제거
시퀀스의 첫 번째 문자가 U+FEFF(바이트 순서 마크)이면 제거됩니다.
CRLF 정규화
Each pair of characters U+000D (CR) immediately followed by U+000A (LF) is replaced by a single U+000A (LF). This happens once, not repeatedly, so after the normalization, there can still exist U+000D (CR) immediately followed by U+000A (LF) in the input (e.g. if the raw input contained “CR CR LF LF”).
U+000D (CR) 문자의 다른 발생은 그대로 유지됩니다(이들은 공백 으로 처리됩니다).
쉬뱅 제거
남은 시퀀스가 #! 문자로 시작하면, 첫 번째 U+000A (LF)까지의 문자가 시퀀스에서 제거됩니다.
예를 들어, 다음 파일의 첫 번째 줄은 무시됩니다:
#!/usr/bin/env rustx
fn main() {
println!("안녕하세요!");
}
예외적으로, #! 문자 뒤에 (중간에 있는 주석 이나 공백 을 무시하고) [ 토큰이 오면 아무것도 제거되지 않습니다. 이는 소스 파일 시작 부분에 있는 내부 속성 이 제거되는 것을 방지합니다.
토큰화
결과 문자 시퀀스는 이 장의 나머지 부분에 설명된 대로 토큰으로 변환됩니다.
Note
The standard library
include!macro applies the following transformations to the file it reads:
- Byte order mark removal.
- CRLF normalization.
- Shebang removal when invoked in an item context (as opposed to expression or statement contexts).
The
include_str!andinclude_bytes!macros do not apply these transformations.
키워드
러스트는 키워드를 세 가지 범주로 나눕니다:
엄격한 키워드
이 키워드는 올바른 컨텍스트에서만 사용할 수 있습니다. 다음 이름으로는 사용할 수 없습니다:
The following keywords are in all editions:
_asasyncawaitbreakconstcontinuecratedynelseenumexternfalsefnforifimplinletloopmatchmodmovemutpubrefreturnselfSelfstaticstructsupertraittruetypeunsafeusewherewhile
2018 Edition differences
The following keywords were added in the 2018 edition:
asyncawaitdyn
예약된 키워드
이 키워드들은 아직 사용되지 않지만, 미래를 위해 예약되어 있습니다. 이 키워드들은 엄격한 키워드와 동일한 제약 조건을 가집니다. 이 키워드들을 사용하지 못하게 함으로써 현재 프로그램이 미래 버전의 Rust와 호환되도록 하기 위함입니다.
abstractbecomeboxdofinalgenmacrooverrideprivtrytypeofunsizedvirtualyield
2018 Edition differences
The
trykeyword was added as a reserved keyword in the 2018 edition.
2024 Edition differences
The
genkeyword was added as a reserved keyword in the 2024 edition.
약한 키워드
이 키워드는 특정 컨텍스트에서만 특별한 의미를 가집니다. 예를 들어, union 이라는 이름으로 변수나 메서드를 선언할 수 있습니다.
'staticmacro_rulesrawsafeunion
macro_rules는 사용자 정의 매크로 를 생성하는 데 사용됩니다.
union은 유니온 을 선언하는 데 사용되며, 유니온 선언에서 사용될 때만 키워드입니다.
-
'static은 정적 라이프타임에 사용되며, 제네릭 라이프타임 매개변수 또는 루프 레이블 로 사용될 수 없습니다.// error[E0262]: 잘못된 라이프타임 매개변수 이름: `'static` fn invalid_lifetime_parameter<'static>(s: &'static str) -> &'static str { s }
safe는 함수와 정적에 사용되며, 외부 블록 에서 의미를 가집니다.
raw는 원시 차용 연산자 에 사용되며, 원시 차용 연산자 형식(예:&raw const expr또는&raw mut expr)과 일치할 때만 키워드입니다.
2018 Edition differences
2015년 에디션에서
dyn은::또는<로 시작하지 않는 경로, 라이프타임, 물음표,for키워드 또는 여는 괄호가 뒤따르는 타입 위치에서 사용될 때 키워드입니다.2018년 에디션부터
dyn은 엄격한 키워드로 승격되었습니다.
식별자
Lexer
IDENTIFIER_OR_KEYWORD → ( XID_Start | _ ) XID_Continue*
XID_Start → <XID_Start defined by Unicode>
XID_Continue → <XID_Continue defined by Unicode>
RAW_IDENTIFIER → r# IDENTIFIER_OR_KEYWORD
NON_KEYWORD_IDENTIFIER → IDENTIFIER_OR_KEYWORDexcept a strict or reserved keyword
IDENTIFIER → NON_KEYWORD_IDENTIFIER | RAW_IDENTIFIER
RESERVED_RAW_IDENTIFIER →
r# ( _ | crate | self | Self | super ) !XID_Continue
Identifiers follow the specification in Unicode Standard Annex #31 for Unicode version 17.0, with the additions described below. Some examples of identifiers:
foo_identifierr#trueМосква東京
UAX #31에서 사용된 프로필은 다음과 같습니다:
- 시작 :=
XID_Start, 더하기 밑줄 문자 (U+005F) - 계속 :=
XID_Continue - 중간 := 비어 있음
Note
Identifiers starting with an underscore are typically used to indicate an identifier that is intentionally unused, and will silence the unused warning in
rustc.
식별자는 아래 원시 식별자 에 설명된 r# 접두사 없이 엄격한 또는 예약된 키워드일 수 없습니다.
제로 너비 비결합자(ZWNJ U+200C) 및 제로 너비 결합자(ZWJ U+200D) 문자는 식별자에 허용되지 않습니다.
식별자는 다음 상황에서 XID_Start 및 XID_Continue 의 ASCII 서브셋으로 제한됩니다:
extern cratedeclarations (except the AsClause identifier)- 경로 에서 참조되는 외부 크레이트 이름
path속성 없이 파일 시스템에서 로드된 모듈 이름no_mangle속성 항목- 외부 블록 의 아이템 이름
정규화
Identifiers are normalized using Normalization Form C (NFC) as defined in Unicode Standard Annex #15. Two identifiers are equal if their NFC forms are equal.
절차적 및 선언적 매크로는 입력에서 정규화된 식별자를 받습니다.
원시 식별자
원시 식별자는 일반 식별자와 같지만 r# 접두사가 붙습니다. (r# 접두사는 실제 식별자의 일부로 포함되지 않습니다.)
일반 식별자와 달리, 원시 식별자는 RAW_IDENTIFIER 에 대해 위에 나열된 키워드를 제외한 모든 엄격하거나 예약된 키워드일 수 있습니다.
It is an error to use the RESERVED_RAW_IDENTIFIER token.
주석
Lexer
COMMENT →
LINE_COMMENT
| INNER_LINE_DOC
| OUTER_LINE_DOC
| INNER_BLOCK_DOC
| OUTER_BLOCK_DOC
| BLOCK_COMMENT
LINE_COMMENT →
// ( ~[/ ! LF] | // ) ~LF*
| // EOF
| //immediately followed by LF
BLOCK_COMMENT →
/**/
| /***/
| /*
^
( ~[* !] | ** | BLOCK_COMMENT_OR_DOC )
( BLOCK_COMMENT_OR_DOC | ~*/ )*
*/
INNER_LINE_DOC →
//! ^ LINE_DOC_COMMENT_CONTENT ( LF | EOF )
LINE_DOC_COMMENT_CONTENT → ( !CR ~LF )*
INNER_BLOCK_DOC →
/*! ^ ( BLOCK_COMMENT_OR_DOC | BLOCK_CHAR )* */
OUTER_LINE_DOC →
/// ^ LINE_DOC_COMMENT_CONTENT ( LF | EOF )
OUTER_BLOCK_DOC →
/** ![* /]
^
( ~* | BLOCK_COMMENT_OR_DOC )
( BLOCK_COMMENT_OR_DOC | BLOCK_CHAR )*
*/
BLOCK_CHAR → ( !( */ | CR ) CHAR )
BLOCK_COMMENT_OR_DOC →
BLOCK_COMMENT
| OUTER_BLOCK_DOC
| INNER_BLOCK_DOC
비 문서 주석
주석은 일반적인 C++ 스타일의 라인 (//) 및 블록 (/* ... */) 주석 형식을 따릅니다. 중첩된 블록 주석이 지원됩니다.
비 문서 주석은 공백의 한 형태로 해석됩니다.
문서 주석
정확히 세 개의 슬래시(///)로 시작하는 라인 문서 주석과 블록 문서 주석(/** ... */), 이 두 가지 외부 문서 주석은 doc 속성 을 위한 특별한 구문으로 해석됩니다.
That is, they are equivalent to writing #[doc="..."] around the body of the comment, i.e., /// Foo turns into #[doc=" Foo"] and /** Bar */ turns into #[doc=" Bar "]. They must therefore appear before something that accepts an outer attribute.
//! 로 시작하는 라인 주석과 /*! ... */ 블록 주석은 뒤따르는 항목이 아닌 주석의 부모에 적용되는 문서 주석입니다.
즉, 이들은 주석 본문 주위에 #![doc="..."] 를 작성하는 것과 동일합니다. //! 주석은 일반적으로 소스 파일을 차지하는 모듈을 문서화하는 데 사용됩니다.
문서 주석에는 U+000D (CR) 문자가 허용되지 않습니다.
Note
It is conventional for doc comments to contain Markdown, as expected by
rustdoc. However, the comment syntax does not respect any internal Markdown./** `glob = "*/*.rs";` */terminates the comment at the first*/, and the remaining code would cause a syntax error. This slightly limits the content of block doc comments compared to line doc comments.
Note
The sequence
U+000D(CR) immediately followed byU+000A(LF) would have been previously transformed into a singleU+000A(LF).
예시
#![allow(unused)]
fn main() {
//! 이 크레이트의 암시적 익명 모듈에 적용되는 문서 주석
pub mod outer_module {
//! - 내부 라인 문서
//!! - 여전히 내부 라인 문서 (하지만 시작에 느낌표가 있음)
/*! - 내부 블록 문서 */
/*!! - 여전히 내부 블록 문서 (하지만 시작에 느낌표가 있음) */
// - 단순 주석
/// - 외부 라인 문서 (정확히 슬래시 3개)
//// - 단순 주석
/* - 단순 주석 */
/** - 외부 블록 문서 (정확히) 별표 2개 */
/*** - 단순 주석 */
pub mod inner_module {}
pub mod nested_comments {
/* Rust에서는 /* /* 주석을 중첩할 수 있습니다 */ */ */
// 세 가지 유형의 블록 주석은 다른 유형 내에 포함되거나 중첩될 수 있습니다:
/* /* */ /** */ /*! */ */
/*! /* */ /** */ /*! */ */
/** /* */ /** */ /*! */ */
pub mod dummy_item {}
}
pub mod degenerate_cases {
// 비어 있는 내부 라인 문서
//!
// 비어 있는 내부 블록 문서
/*!*/
// 비어 있는 라인 주석
//
// 비어 있는 외부 라인 문서
///
// 비어 있는 블록 주석
/**/
pub mod dummy_item {}
// 비어 있는 2-별표 블록은 문서 블록이 아니라 블록 주석입니다.
/***/
}
/* 다음은 외부 문서 주석이 문서를 받을 항목을 요구하기 때문에 허용되지 않습니다 */
/// 내 항목은 어디에 있나요?
mod boo {}
}
}
공백
Lexer
WHITESPACE →
U+0009 // Horizontal tab, '\t'
| U+000A // Line feed, '\n'
| U+000B // Vertical tab
| U+000C // Form feed
| U+000D // Carriage return, '\r'
| U+0020 // Space, ' '
| U+0085 // Next line
| U+200E // Left-to-right mark
| U+200F // Right-to-left mark
| U+2028 // Line separator
| U+2029 // Paragraph separator
TAB → U+0009 // Horizontal tab, '\t'
LF → U+000A // Line feed, '\n'
CR → U+000D // Carriage return, '\r'
Whitespace is any non-empty string containing only characters that have the Pattern_White_Space Unicode property.
Rust는 “자유 형식” 언어입니다. 즉, 모든 형태의 공백은 문법에서 토큰 을 구분하는 역할만 하며, 의미론적 중요성은 없습니다.
Rust 프로그램은 각 공백 요소가 단일 공백 문자처럼 다른 유효한 공백 요소로 대체되어도 동일한 의미를 가집니다.
토큰
Lexer
Token →
RESERVED_TOKEN
| RAW_IDENTIFIER
| CHAR_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| C_STRING_LITERAL
| RAW_C_STRING_LITERAL
| FLOAT_LITERAL
| INTEGER_LITERAL
| LIFETIME_TOKEN
| PUNCTUATION
| IDENTIFIER_OR_KEYWORD
토큰은 정규 (비재귀) 언어로 정의된 문법의 기본 생성물입니다. Rust 소스 입력은 다음 종류의 토큰으로 나눌 수 있습니다:
이 문서의 문법에서 “단순” 토큰은 문자열 테이블 생성 형식으로 주어지며, monospace 글꼴로 나타납니다.
리터럴
리터럴은 리터럴 표현식 에 사용되는 토큰입니다.
예시
문자와 문자열
| 예시 | # 세트 1 | 문자 | 이스케이프 | |
|---|---|---|---|---|
| 문자 | 'H' | 0 | 모든 유니코드 | 인용 & ASCII & 유니코드 |
| 문자열 | "hello" | 0 | 모든 유니코드 | 인용 & ASCII & 유니코드 |
| 원시 문자열 | r#"hello"# | <256 | 모든 유니코드 | N/A |
| 바이트 | b'H' | 0 | 모든 ASCII | 인용 & 바이트 |
| 바이트 문자열 | b"hello" | 0 | 모든 ASCII | 인용 & 바이트 |
| 원시 바이트 문자열 | br#"hello"# | <256 | 모든 ASCII | N/A |
| C 문자열 | c"hello" | 0 | 모든 유니코드 | 인용 & 바이트 & 유니코드 |
| 원시 C 문자열 | cr#"hello"# | <256 | 모든 유니코드 | N/A |
ASCII 이스케이프
| 이름 | |
|---|---|
\x41 | 7-bit character code (exactly 2 hex digits, up to 0x7F) |
\n | 새 줄 |
\r | 캐리지 리턴 |
\t | 탭 |
\\ | 백슬래시 |
\0 | 널 |
바이트 이스케이프
| 이름 | |
|---|---|
\x7F | 8-bit character code (exactly 2 hex digits) |
\n | 새 줄 |
\r | 캐리지 리턴 |
\t | 탭 |
\\ | 백슬래시 |
\0 | 널 |
유니코드 이스케이프
| 이름 | |
|---|---|
\u{7FFF} | 24-bit Unicode character code (up to 6 hex digits) |
인용 이스케이프
| 이름 | |
|---|---|
\' | 작은따옴표 |
| 큰따옴표 | 큰따옴표 |
숫자
접미사
접미사는 리터럴의 주요 부분 뒤에 오는 문자 시퀀스(중간 공백 없이)로, 비원시 식별자 또는 키워드와 동일한 형태입니다.
Lexer
SUFFIX → IDENTIFIER_OR_KEYWORDexcept _
SUFFIX_NO_E → ![e E] SUFFIX
어떤 종류의 리터럴(문자열, 정수 등)이든 어떤 접미사와 함께 사용되어도 유효한 토큰입니다.
어떤 접미사가 붙은 리터럴 토큰도 오류 없이 매크로에 전달될 수 있습니다. 매크로 자체는 그러한 토큰을 해석하는 방법과 오류를 발생시킬지 여부를 결정합니다. 특히, 예제 매크로의 literal 프래그먼트 지정자는 임의의 접미사가 붙은 리터럴 토큰과 일치합니다.
#![allow(unused)]
fn main() {
macro_rules! blackhole { ($tt:tt) => () }
macro_rules! blackhole_lit { ($l:literal) => () }
blackhole!("string"suffix); // OK
blackhole_lit!(1suffix); // OK
}
그러나 리터럴 표현식 또는 패턴으로 해석되는 리터럴 토큰의 접미사는 제한됩니다. 비숫자 리터럴 토큰의 모든 접미사는 거부되며, 숫자 리터럴 토큰은 아래 목록의 접미사만 허용됩니다.
| 정수 | 부동 소수점 |
|---|---|
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize | f32, f64 |
문자 및 문자열 리터럴
문자 리터럴
Lexer
CHAR_LITERAL →
‘
( ~[’ \ LF CR TAB] | QUOTE_ESCAPE | ASCII_ESCAPE | UNICODE_ESCAPE )
’ SUFFIX?
QUOTE_ESCAPE → \’ | \“
ASCII_ESCAPE →
\x OCT_DIGIT HEX_DIGIT
| \n | \r | \t | \\ | \0
UNICODE_ESCAPE →
\u{ ( HEX_DIGIT _* )1..=6valid hex char value }3
문자 리터럴 은 두 개의 U+0027(작은따옴표) 문자 안에 묶인 단일 유니코드 문자입니다. 단, U+0027 자체는 선행하는 U+005C 문자(\)로 이스케이프 되어야 합니다.
문자열 리터럴
Lexer
STRING_LITERAL →
“ (
~[” \ CR]
| QUOTE_ESCAPE
| ASCII_ESCAPE
| UNICODE_ESCAPE
| STRING_CONTINUE
)* “ SUFFIX?
STRING_CONTINUE → \ LF
문자열 리터럴 은 두 개의 U+0022(큰따옴표) 문자 안에 묶인 모든 유니코드 문자 시퀀스입니다. 단, U+0022 자체는 선행하는 U+005C 문자(\)로 이스케이프 되어야 합니다.
Line-breaks, represented by the character U+000A (LF), are allowed in string literals. The character U+000D (CR) may not appear in a string literal. When an unescaped U+005C character (\) occurs immediately before a line break, the line break does not appear in the string represented by the token. See String continuation escapes for details.
문자 이스케이프
문자 또는 비원시 문자열 리터럴에는 몇 가지 추가 이스케이프 가 사용 가능합니다. 이스케이프는 U+005C(\)로 시작하며 다음 형식 중 하나로 이어집니다:
- 7비트 코드 포인트 이스케이프 는
U+0078(x)로 시작하며,0x7F까지의 값을 가진 정확히 두 개의 16진수 숫자 가 뒤따릅니다. 이는 제공된 16진수 값과 동일한 값을 가진 ASCII 문자를 나타냅니다. 더 높은 값은 유니코드 코드 포인트를 의미하는지 바이트 값을 의미하는지 모호하기 때문에 허용되지 않습니다.
- A 24-bit code point escape starts with
U+0075(u) and is followed by up to six hex digits surrounded by bracesU+007B({) andU+007D(}). It denotes the Unicode code point equal to the provided hex value. The value must be a valid Unicode scalar value.
- 공백 이스케이프 는
U+006E(n),U+0072(r), 또는U+0074(t) 문자 중 하나이며, 각각 유니코드 값U+000A(LF),U+000D(CR) 또는U+0009(HT)를 나타냅니다.
- 널 이스케이프 는
U+0030(0) 문자이며 유니코드 값U+0000(NUL)을 나타냅니다.
- The backslash escape is the character
U+005C(\) which must be escaped in order to denote itself.
원시 문자열 리터럴
Lexer
RAW_STRING_LITERAL →
r “ ^ RAW_STRING_CONTENT ” SUFFIX?
| r #n:1..=255 ^ “ RAW_STRING_CONTENT_HASHED ” #n SUFFIX?
RAW_STRING_CONTENT → ( !“ ~CR )*
RAW_STRING_CONTENT_HASHED → ( !( “ #n ) ~CR )*
원시 문자열 리터럴은 어떤 이스케이프도 처리하지 않습니다. 이들은 U+0072(r) 문자로 시작하며, 256개 미만의 U+0023(#) 문자와 U+0022(큰따옴표) 문자가 뒤따릅니다.
원시 문자열 본문 은 U+000D(CR)를 제외한 모든 유니코드 문자 시퀀스를 포함할 수 있습니다. 이스케이프는 다른 U+0022(큰따옴표) 문자로만 종료되며, 여는 U+0022(큰따옴표) 문자 앞에 있던 것과 동일한 수의 U+0023(#) 문자가 뒤따릅니다.
원시 문자열 본문에 포함된 모든 유니코드 문자는 그 자체를 나타내며, U+0022(큰따옴표) 문자(원시 문자열 리터럴을 시작하는 데 사용된 U+0023(#) 문자 수만큼 이상이 뒤따르지 않는 경우) 또는 U+005C(\)는 특별한 의미를 갖지 않습니다.
문자열 리터럴 예시:
#![allow(unused)]
fn main() {
"foo"; r"foo"; // foo
"\"foo\""; r#""foo""#; // "foo"
"foo #\"# bar";
r##"foo #"# bar"##; // foo #"# bar
"\x52"; "R"; r"R"; // R
"\\x52"; r"\x52"; // \x52
}
바이트 및 바이트 문자열 리터럴
바이트 리터럴
Lexer
BYTE_LITERAL →
b’ ^ ( ASCII_FOR_CHAR | BYTE_ESCAPE ) ’ SUFFIX?
ASCII_FOR_CHAR → ![’ \ LF CR TAB] ASCII
BYTE_ESCAPE →
\x HEX_DIGIT HEX_DIGIT
| \n | \r | \t | \\ | \0 | \’ | \“
바이트 리터럴 은 U+0062(b)와 U+0027(작은따옴표) 문자로 시작하고 U+0027 문자로 끝나는 단일 ASCII 문자(U+0000 에서 U+007F 범위) 또는 단일 이스케이프 입니다. U+0027 문자가 리터럴 내에 있으면 선행하는 U+005C(\) 문자로 이스케이프 되어야 합니다. 이는 u8 부호 없는 8비트 정수 숫자 리터럴 과 동일합니다.
바이트 문자열 리터럴
Lexer
BYTE_STRING_LITERAL →
b“ ^ ( ASCII_FOR_STRING | BYTE_ESCAPE | STRING_CONTINUE )* “ SUFFIX?
ASCII_FOR_STRING → ![“ \ CR] ASCII
비원시 바이트 문자열 리터럴 은 U+0062(b)와 U+0022(큰따옴표) 문자로 시작하고 U+0022 문자로 끝나는 ASCII 문자와 이스케이프 시퀀스입니다. U+0022 문자가 리터럴 내에 있으면 선행하는 U+005C(\) 문자로 이스케이프 되어야 합니다. 또는 바이트 문자열 리터럴은 아래에 정의된 원시 바이트 문자열 리터럴 일 수 있습니다.
Line-breaks, represented by the character U+000A (LF), are allowed in byte string literals. The character U+000D (CR) may not appear in a byte string literal. When an unescaped U+005C character (\) occurs immediately before a line break, the line break does not appear in the string represented by the token. See String continuation escapes for details.
바이트 또는 비원시 바이트 문자열 리터럴에는 몇 가지 추가 이스케이프 가 사용 가능합니다. 이스케이프는 U+005C(\)로 시작하며 다음 형식 중 하나로 이어집니다:
- 바이트 이스케이프 는
U+0078(x)로 시작하며, 정확히 두 개의 16진수 숫자 가 뒤따릅니다. 이는 제공된 16진수 값과 동일한 바이트를 나타냅니다.
- 공백 이스케이프 는
U+006E(n),U+0072(r), 또는U+0074(t) 문자 중 하나이며, 각각 바이트 값0x0A(ASCII LF),0x0D(ASCII CR) 또는0x09(ASCII HT)를 나타냅니다.
- 널 이스케이프 는
U+0030(0) 문자이며 바이트 값0x00(ASCII NUL)을 나타냅니다.
- The backslash escape is the character
U+005C(\) which must be escaped in order to denote its ASCII encoding0x5C.
원시 바이트 문자열 리터럴
Lexer
RAW_BYTE_STRING_LITERAL →
br “ ^ RAW_BYTE_STRING_CONTENT ” SUFFIX?
| br #n:1..=255 ^ “ RAW_BYTE_STRING_CONTENT_HASHED ” #n SUFFIX?
RAW_BYTE_STRING_CONTENT → ( !“ ASCII_FOR_RAW )*
RAW_BYTE_STRING_CONTENT_HASHED → ( !( “ #n ) ASCII_FOR_RAW )*
ASCII_FOR_RAW → !CR ASCII
원시 바이트 문자열 리터럴은 어떤 이스케이프도 처리하지 않습니다. 이들은 U+0062(b) 문자로 시작하고 U+0072(r) 문자가 뒤따르며, 256개 미만의 U+0023(#) 문자와 U+0022(큰따옴표) 문자가 뒤따릅니다.
원시 문자열 본문 은 U+000D(CR)를 제외한 모든 ASCII 문자 시퀀스를 포함할 수 있습니다. 이스케이프는 다른 U+0022(큰따옴표) 문자로만 종료되며, 여는 U+0022(큰따옴표) 문자 앞에 있던 것과 동일한 수의 U+0023(#) 문자가 뒤따릅니다. 원시 바이트 문자열 리터럴은 비 ASCII 바이트를 포함할 수 없습니다.
원시 문자열 본문에 포함된 모든 문자는 ASCII 인코딩을 나타내며, U+0022(큰따옴표) 문자(원시 문자열 리터럴을 시작하는 데 사용된 U+0023(#) 문자 수만큼 이상이 뒤따르지 않는 경우) 또는 U+005C(\)는 특별한 의미를 갖지 않습니다.
바이트 문자열 리터럴 예시:
#![allow(unused)]
fn main() {
b"foo"; br"foo"; // foo
b"\"foo\""; br#""foo""#; // "foo"
b"foo #\"# bar";
br##"foo #"# bar"##; // foo #"# bar
b"\x52"; b"R"; br"R"; // R
b"\\x52"; br"\x52"; // \x52
}
C 문자열 및 원시 C 문자열 리터럴
C 문자열 리터럴
Lexer
C_STRING_LITERAL →
c“ ^ (
~[“ \ CR NUL]
| BYTE_ESCAPEexcept \0 or \x00
| UNICODE_ESCAPEexcept \u{0}, \u{00}, …, \u{000000}
| STRING_CONTINUE
)* ” SUFFIX?
C 문자열 리터럴 은 U+0063(c)과 U+0022(큰따옴표) 문자로 시작하고 U+0022 문자로 끝나는 유니코드 문자와 이스케이프 시퀀스입니다. U+0022 문자가 리터럴 내에 있으면 선행하는 U+005C(\) 문자로 이스케이프 되어야 합니다. 또는 C 문자열 리터럴은 아래에 정의된 원시 C 문자열 리터럴 일 수 있습니다.
C 문자열은 바이트 0x00 으로 암시적으로 종료되므로, C 문자열 리터럴 c"" 는 바이트 문자열 리터럴 b"\x00" 에서 &CStr 를 수동으로 구성하는 것과 동일합니다. 암시적 종료자를 제외하고, 바이트 0x00 은 C 문자열 내에서 허용되지 않습니다.
Line-breaks, represented by the character U+000A (LF), are allowed in C string literals. The character U+000D (CR) may not appear in a C string literal. When an unescaped U+005C character (\) occurs immediately before a line break, the line break does not appear in the string represented by the token. See String continuation escapes for details.
비원시 C 문자열 리터럴에는 몇 가지 추가 이스케이프 가 사용 가능합니다. 이스케이프는 U+005C(\)로 시작하며 다음 형식 중 하나로 이어집니다:
- 바이트 이스케이프 는
U+0078(x)로 시작하며, 정확히 두 개의 16진수 숫자 가 뒤따릅니다. 이는 제공된 16진수 값과 동일한 바이트를 나타냅니다.
- 24비트 코드 포인트 이스케이프 는
U+0075(u)로 시작하고 중괄호U+007B({) 및U+007D(})로 둘러싸인 최대 6개의 16진수 숫자 가 뒤따릅니다. 제공된 16진수 값과 동일한 유니코드 코드 포인트를 나타내며 UTF-8로 인코딩됩니다.
- 공백 이스케이프 는
U+006E(n),U+0072(r), 또는U+0074(t) 문자 중 하나이며, 각각 바이트 값0x0A(ASCII LF),0x0D(ASCII CR) 또는0x09(ASCII HT)를 나타냅니다.
- The backslash escape is the character
U+005C(\) which must be escaped in order to denote its ASCII encoding0x5C.
C 문자열은 정의된 인코딩이 없는 바이트를 나타내지만 C 문자열 리터럴에는 U+007F 이상의 유니코드 문자가 포함될 수 있습니다. 이러한 문자는 해당 문자의 UTF-8 표현 바이트로 대체됩니다.
다음 C 문자열 리터럴은 동일합니다.
#![allow(unused)]
fn main() {
c"æ"; // 라틴어 소문자 AE (U+00E6)
c"\u{00E6}";
c"\xC3\xA6";
}
2021 Edition differences
C string literals are accepted in the 2021 edition or later. In earlier editions the token
c""is lexed asc "".
원시 C 문자열 리터럴
Lexer
RAW_C_STRING_LITERAL →
cr “ ^ RAW_C_STRING_CONTENT ” SUFFIX?
| cr #n:1..=255 ^ “ RAW_C_STRING_CONTENT_HASHED ” #n SUFFIX?
RAW_C_STRING_CONTENT → ( !“ ~[CR NUL] )*
RAW_C_STRING_CONTENT_HASHED → ( !( “ #n ) ~[CR NUL] )*
원시 C 문자열 리터럴은 어떤 이스케이프도 처리하지 않습니다. U+0063(c) 문자로 시작하고 U+0072(r) 문자가 뒤따르며, 256자 미만의 U+0023(#) 문자와 U+0022(큰따옴표) 문자가 뒤따릅니다.
원시 C 문자열 본문 은 U+0000(NUL) 및 U+000D(CR) 이외의 모든 유니코드 문자 시퀀스를 포함할 수 있습니다. 다른 U+0022(큰따옴표) 문자로만 종료되며, 여는 U+0022(큰따옴표) 문자 앞에 있던 것과 동일한 수의 U+0023(#) 문자가 뒤따릅니다.
원시 C 문자열 본문에 포함된 모든 문자는 UTF-8 인코딩으로 자신을 나타냅니다. U+0022(큰따옴표) 문자(원시 C 문자열 리터럴을 시작하는 데 사용된 U+0023(#) 문자 수만큼 이상 뒤따르지 않는 경우) 또는 U+005C(\)는 특별한 의미를 갖지 않습니다.
2021 Edition differences
Raw C string literals are accepted in the 2021 edition or later. In earlier editions the token
cr""is lexed ascr "", andcr#""#is lexed ascr #""#(which is non-grammatical).
C 문자열 및 원시 C 문자열 리터럴의 예
#![allow(unused)]
fn main() {
c"foo"; cr"foo"; // foo
c"\"foo\""; cr#""foo""#; // "foo"
c"foo #\"# bar";
cr##"foo #"# bar"##; // foo #"# bar
c"\x52"; c"R"; cr"R"; // R
c"\\x52"; cr"\x52"; // \x52
}
숫자 리터럴
숫자 리터럴 은 정수 리터럴 또는 부동 소수점 리터럴 입니다. 두 종류의 리터럴을 인식하는 문법은 혼합되어 있습니다.
정수 리터럴
Lexer
INTEGER_LITERAL →
( BIN_LITERAL | OCT_LITERAL | HEX_LITERAL | DEC_LITERAL ) SUFFIX_NO_E?
DEC_LITERAL → DEC_DIGIT ( DEC_DIGIT | _ )*
BIN_LITERAL → 0b _* BIN_DIGIT ( BIN_DIGIT | _ )*
OCT_LITERAL → 0o _* OCT_DIGIT ( OCT_DIGIT | _ )*
HEX_LITERAL → 0x _* HEX_DIGIT ( HEX_DIGIT | _ )*
BIN_DIGIT → [0-1]
OCT_DIGIT → [0-7]
DEC_DIGIT → [0-9]
HEX_DIGIT → [0-9 a-f A-F]
정수 리터럴 은 네 가지 형태 중 하나를 갖습니다.
- 십진수 리터럴 은 십진수 숫자 로 시작하고 십진수 숫자 와 밑줄 의 혼합으로 이어집니다.
- 16진수 리터럴 은 문자 시퀀스
U+0030U+0078(0x)로 시작하고 16진수 숫자와 밑줄의 혼합(최소 한 자리 이상)으로 이어집니다.
- 8진수 리터럴 은 문자 시퀀스
U+0030U+006F(0o)로 시작하고 8진수 숫자와 밑줄의 혼합(최소 한 자리 이상)으로 이어집니다.
- 2진수 리터럴 은 문자 시퀀스
U+0030U+0062(0b)로 시작하고 2진수 숫자와 밑줄의 혼합(최소 한 자리 이상)으로 이어집니다.
모든 리터럴과 마찬가지로 정수 리터럴 뒤에는 위에서 설명한 대로 접미사가 (공백 없이) 바로 올 수 있습니다. 접미사는 e 또는 E 로 시작할 수 없습니다. 이는 부동 소수점 리터럴의 지수로 해석되기 때문입니다. 이러한 접미사의 효과에 대해서는 정수 리터럴 표현식 을 참조하십시오.
리터럴 표현식으로 허용되는 정수 리터럴의 예:
#![allow(unused)]
fn main() {
#![allow(overflowing_literals)]
123;
123i32;
123u32;
123_u32;
0xff;
0xff_u8;
0x01_f32; // 정수 7986, 부동 소수점 1.0 아님
0x01_e3; // 정수 483, 부동 소수점 1000.0 아님
0o70;
0o70_i16;
0b1111_1111_1001_0000;
0b1111_1111_1001_0000i64;
0b________1;
0usize;
// 이것들은 타입에 비해 너무 크지만, 리터럴 표현식으로 허용됩니다.
128_i8;
256_u8;
// 이것은 정수 리터럴이며, 부동 소수점 리터럴 표현식으로 허용됩니다.
5f32;
}
예를 들어 -1i8 은 - 와 1i8 의 두 토큰으로 분석됩니다.
리터럴 표현식으로 허용되지 않는 정수 리터럴의 예:
#![allow(unused)]
fn main() {
#[cfg(false)] {
0invalidSuffix;
123AFB43;
0b010a;
0xAB_CD_EF_GH;
0b1111_f32;
}
}
튜플 인덱스
Lexer
TUPLE_INDEX → DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL
A tuple index is used to refer to the fields of tuples, tuple structs, and tuple enum variants.
튜플 인덱스는 리터럴 토큰과 직접 비교됩니다. 튜플 인덱스는 0 으로 시작하고 각 연속 인덱스는 값을 1씩 10진수 값으로 증가시킵니다. 따라서 10진수 값만 일치하며 값에 추가 0 접두사 문자가 없어야 합니다.
Tuple indices may not include any suffixes (such as usize).
#![allow(unused)]
fn main() {
let example = (개, "cat", 말);
let dog = example.0;
let cat = example.1;
// 다음 예제는 유효하지 않습니다.
let cat = example.01; // 오류: `01` 이라는 필드가 없습니다
let horse = example.0b10; // 오류: `0b10` 이라는 필드가 없습니다
let unicorn = example.0usize; // ERROR suffixes on a tuple index are invalid
let underscore = example.0_0; // ERROR no field `0_0` on type `(&str, &str, &str)`
}
부동 소수점 리터럴
Lexer
FLOAT_LITERAL →
DEC_LITERAL ( . DEC_LITERAL )? FLOAT_EXPONENT SUFFIX?
| DEC_LITERAL . DEC_LITERAL SUFFIX_NO_E?
| DEC_LITERAL . !( . | _ | XID_Start )
FLOAT_EXPONENT →
( e | E ) ^ ( + | - )? _* DEC_DIGIT ( DEC_DIGIT | _ )*
부동 소수점 리터럴 은 두 가지 형태 중 하나를 갖습니다.
- 십진수 리터럴 뒤에 마침표 문자
U+002E(.)가 옵니다. 선택적으로 다른 십진수 리터럴과 선택적 지수 가 뒤따를 수 있습니다. - 단일 십진수 리터럴 뒤에 지수 가 옵니다.
정수 리터럴과 마찬가지로 부동 소수점 리터럴 뒤에는 접미사가 올 수 있습니다. 단, 접미사 앞부분이 U+002E(.)로 끝나지 않아야 합니다. 리터럴에 지수가 포함되지 않은 경우 접미사는 e 또는 E 로 시작할 수 없습니다. 이러한 접미사의 효과에 대해서는 부동 소수점 리터럴 표현식 을 참조하십시오.
리터럴 표현식으로 허용되는 부동 소수점 리터럴의 예:
#![allow(unused)]
fn main() {
123.0f64;
0.1f64;
0.1f32;
12E+99_f64;
let x: f64 = 2.;
}
This last example is different because it is not possible to use the suffix syntax with a floating point literal ending in a period. 2.f64 would attempt to call a method named f64 on 2.
예를 들어 -1.0 은 - 와 1.0 의 두 토큰으로 분석됩니다.
리터럴 표현식으로 허용되지 않는 부동 소수점 리터럴의 예:
#![allow(unused)]
fn main() {
#[cfg(false)] {
2.0f80;
2e5f80;
2e5e6;
2.0e5e6;
1.3e10u64;
}
}
숫자 리터럴과 유사한 예약된 형식
Lexer
RESERVED_NUMBER →
BIN_LITERAL [2-9]
| OCT_LITERAL [8-9]
| ( BIN_LITERAL | OCT_LITERAL | HEX_LITERAL ) . !( . | _ | XID_Start )
| ( BIN_LITERAL | OCT_LITERAL ) ( e | E )
| 0b _* !BIN_DIGIT
| 0o _* !OCT_DIGIT
| 0x _* !HEX_DIGIT
숫자 리터럴과 유사한 다음 어휘 형식은 예약된 형식 입니다. 이들이 제기하는 모호성 때문에 토크나이저는 이를 별도의 토큰으로 해석하는 대신 거부합니다.
- 접미사가 없는 2진수 또는 8진수 리터럴 뒤에 공백 없이 해당 기수 범위를 벗어나는 10진수 숫자가 오는 경우.
- 접미사가 없는 2진수, 8진수 또는 16진수 리터럴 뒤에 공백 없이 마침표 문자가 오는 경우(마침표 뒤에 오는 내용에 대한 제한은 부동 소수점 리터럴과 동일).
- 접미사가 없는 2진수 또는 8진수 리터럴 뒤에 공백 없이
e또는E문자가 오는 경우.
- 기수 접두사 중 하나로 시작하지만 유효한 2진수, 8진수 또는 16진수 리터럴이 아닌 입력 (숫자를 포함하지 않기 때문에).
- 지수에 숫자가 없는 부동 소수점 리터럴 형식을 갖는 입력.
예약된 형식의 예:
#![allow(unused)]
fn main() {
0b0102; // 이것은 `0b010` 뒤에 `2` 가 오는 것이 아닙니다
0o1279; // 이것은 `0o127` 뒤에 `9` 가 오는 것이 아닙니다
0x80.0; // 이것은 `0x80` 뒤에 `.` 와 `0` 이 오는 것이 아닙니다
0b101e; // 이것은 접미사가 붙은 리터럴이 아니거나, `0b101` 뒤에 `e` 가 오는 것이 아닙니다
0b; // 이것은 정수 리터럴이 아니거나, `0` 뒤에 `b` 가 오는 것이 아닙니다
0b_; // 이것은 정수 리터럴이 아니거나, `0` 뒤에 `b_` 가 오는 것이 아닙니다
2e; // 이것은 부동 소수점 리터럴이 아니거나, `2` 뒤에 `e` 가 오는 것이 아닙니다
2.0e; // 이것은 부동 소수점 리터럴이 아니거나, `2.0` 뒤에 `e` 가 오는 것이 아닙니다
2em; // 이것은 접미사가 붙은 리터럴이 아니거나, `2` 뒤에 `em` 이 오는 것이 아닙니다
2.0em; // 이것은 접미사가 붙은 리터럴이 아니거나, `2.0` 뒤에 `em` 이 오는 것이 아닙니다
}
라이프타임과 루프 레이블
Lexer
LIFETIME_TOKEN →
RAW_LIFETIME
| ‘ IDENTIFIER_OR_KEYWORD !’
LIFETIME_OR_LABEL →
RAW_LIFETIME
| ‘ NON_KEYWORD_IDENTIFIER !’
RAW_LIFETIME →
‘r# ^ IDENTIFIER_OR_KEYWORD !’
RESERVED_RAW_LIFETIME → ‘r# ( _ | crate | self | Self | super ) !( ’ | XID_Continue )
Lifetime parameters and loop labels use LIFETIME_OR_LABEL tokens. Any LIFETIME_TOKEN will be accepted by the lexer, and for example, can be used in macros.
원시 라이프타임은 일반 라이프타임과 같지만 식별자 앞에 r# 접두사가 붙습니다. (r# 접두사는 실제 라이프타임의 일부로 포함되지 않습니다.)
일반 라이프타임과 달리 원시 라이프타임은 RAW_LIFETIME 에 대해 위에 나열된 키워드를 제외한 모든 엄격하거나 예약된 키워드일 수 있습니다.
It is an error to use the RESERVED_RAW_LIFETIME token.
2021 Edition differences
Raw lifetimes are accepted in the 2021 edition or later. In earlier editions the token
'r#ltis lexed as'r # lt.
구두점
Punctuation tokens are used as operators, separators, and other parts of the grammar.
Lexer
PUNCTUATION →
…
| ..=
| <<=
| >>=
| !=
| %=
| &&
| &=
| *=
| +=
| -=
| ->
| ..
| /=
| ::
| <-
| <<
| <=
| ==
| =>
| >=
| >>
| ^=
| |=
| ||
| !
| #
| $
| %
| &
| (
| )
| *
| +
| ,
| -
| .
| /
| :
| ;
| <
| =
| >
| ?
| @
| [
| ]
| ^
| {
| |
| }
| ~
Note
See the syntax index for links to how punctuation characters are used.
구분자
대괄호 구두점은 문법의 다양한 부분에서 사용됩니다. 여는 대괄호는 항상 닫는 대괄호와 짝을 이루어야 합니다. 대괄호와 그 안의 토큰은 매크로 에서 “토큰 트리“라고 합니다. 세 가지 유형의 대괄호는 다음과 같습니다.
| 대괄호 | 유형 |
|---|---|
{ } | 중괄호 |
[ ] | 대괄호 |
( ) | 괄호 |
Reserved tokens
Several token forms are reserved for future use or to avoid confusion. It is an error for the source input to match one of these forms.
Lexer
RESERVED_TOKEN →
RESERVED_GUARDED_STRING_LITERAL
| RESERVED_NUMBER
| RESERVED_POUNDS
| RESERVED_RAW_IDENTIFIER
| RESERVED_RAW_LIFETIME
| RESERVED_TOKEN_DOUBLE_QUOTE
| RESERVED_TOKEN_LIFETIME
| RESERVED_TOKEN_POUND
| RESERVED_TOKEN_SINGLE_QUOTE
예약된 접두사
Lexer
RESERVED_TOKEN_DOUBLE_QUOTE →
IDENTIFIER_OR_KEYWORDexcept b or c or r or br or cr “
RESERVED_TOKEN_SINGLE_QUOTE →
IDENTIFIER_OR_KEYWORDexcept b ’
RESERVED_TOKEN_POUND →
IDENTIFIER_OR_KEYWORDexcept r or br or cr #
RESERVED_TOKEN_LIFETIME →
’ IDENTIFIER_OR_KEYWORDexcept r #
예약된 접두사 로 알려진 일부 어휘 형식은 향후 사용을 위해 예약되어 있습니다.
Source input which would otherwise be lexically interpreted as a non-raw identifier (or a keyword) which is immediately followed by a #, ', or " character (without intervening whitespace) is identified as a reserved prefix.
원시 식별자, 원시 문자열 리터럴 및 원시 바이트 문자열 리터럴에는 # 문자가 포함될 수 있지만 예약된 접두사를 포함하는 것으로 해석되지 않습니다.
마찬가지로 원시 문자열 리터럴, 바이트 리터럴, 바이트 문자열 리터럴, 원시 바이트 문자열 리터럴, C 문자열 리터럴 및 원시 C 문자열 리터럴에 사용되는 r, b, br, c 및 cr 접두사는 예약된 접두사로 해석되지 않습니다.
Source input which would otherwise be lexically interpreted as a non-raw lifetime (or a keyword) which is immediately followed by a # character (without intervening whitespace) is identified as a reserved lifetime prefix.
2021 Edition differences
Starting with the 2021 edition, reserved prefixes are reported as an error by the lexer (in particular, they cannot be passed to macros).
2021 에디션 이전에는 예약된 접두사가 렉서에 의해 허용되고 여러 토큰으로 해석됩니다(예: 식별자 또는 키워드에 대한 하나의 토큰 뒤에
#토큰이 옴).모든 에디션에서 허용되는 예:
#![allow(unused)] fn main() { macro_rules! lexes {($($_:tt)*) => {}} lexes!{a #foo} lexes!{continue 'foo} lexes!{match "..." {}} lexes!{r#let#foo} // 세 개의 토큰: r#let # foo lexes!{'prefix #lt} }2021 에디션 이전에는 허용되었지만 이후에는 거부된 예:
#![allow(unused)] fn main() { macro_rules! lexes {($($_:tt)*) => {}} lexes!{a#foo} lexes!{continue'foo} lexes!{match"..." {}} lexes!{'prefix#lt} }
예약된 가드
Lexer
RESERVED_GUARDED_STRING_LITERAL → #+ STRING_LITERAL
RESERVED_POUNDS → #2..
예약된 가드는 향후 사용을 위해 예약된 구문이며 사용하면 컴파일 오류가 발생합니다.
The reserved guarded string literal is a token of one or more U+0023 (#) immediately followed by a STRING_LITERAL.
예약된 파운드 는 두 개 이상의 U+0023(#) 토큰입니다.
2024 Edition differences
Before the 2024 edition, reserved guards are accepted by the lexer and interpreted as multiple tokens. For example, the
#"foo"#form is interpreted as three tokens.##is interpreted as two tokens.
매크로
Rust의 기능과 구문은 매크로라는 사용자 지정 정의로 확장할 수 있습니다. 이름이 주어지고 some_extension!(...) 과 같은 일관된 구문을 통해 호출됩니다.
새 매크로를 정의하는 두 가지 방법이 있습니다.
- 예제를 통한 매크로 는 더 높은 수준의 선언적 방식으로 새로운 구문을 정의합니다.
- 절차적 매크로 는 입력 토큰에 대해 작동하는 함수를 사용하여 함수와 유사한 매크로, 사용자 지정 파생 및 사용자 지정 속성을 정의합니다.
Macro invocation
Syntax
MacroInvocation →
SimplePath ! DelimTokenTree
DelimTokenTree →
( TokenTree* )
| [ TokenTree* ]
| { TokenTree* }
TokenTree →
Tokenexcept delimiters | DelimTokenTree
MacroInvocationSemi →
SimplePath ! ( TokenTree* ) ;
| SimplePath ! [ TokenTree* ] ;
| SimplePath ! { TokenTree* }
매크로 호출은 컴파일 타임에 매크로를 확장하고 호출을 매크로의 결과로 바꿉니다. 다음과 같은 상황에서 매크로를 호출할 수 있습니다.
macro_rules트랜스크라이버
When used as an item or a statement, the MacroInvocationSemi form is used where a semicolon is required at the end when not using curly braces. Visibility qualifiers are never allowed before a macro invocation or macro_rules definition.
#![allow(unused)]
fn main() {
// 표현식으로 사용됩니다.
let x = vec![1,2,3];
// 문으로 사용됩니다.
println!("안녕하세요!");
// 패턴에서 사용됩니다.
macro_rules! pat {
($i:ident) => (Some($i))
}
if let pat!(x) = Some(1) {
assert_eq!(x, 1);
}
// 유형에서 사용됩니다.
macro_rules! Tuple {
{ $A:ty, $B:ty } => { ($A, $B) };
}
type N2 = Tuple!(i32, i32);
// 항목으로 사용됩니다.
use std::cell::RefCell;
thread_local!(static FOO: RefCell<u32> = RefCell::new(1));
// Used as an associated item.
macro_rules! const_maker {
($t:ty, $v:tt) => { const CONST: $t = $v; };
}
trait T {
const_maker!{i32, 7}
}
// Macro calls within macros.
macro_rules! example {
() => { println!("매크로 내의 매크로 호출!") };
}
// 외부 매크로 `example` 이 확장된 후, 내부 매크로 `println` 이 확장됩니다.
example!();
}
Macros invocations can be resolved via two kinds of scopes:
- 텍스트 범위
- Path-based scope
Macros by example
Syntax
MacroRulesDefinition →
macro_rules ! IDENTIFIER MacroRulesDef
MacroRulesDef →
( MacroRules ) ;
| [ MacroRules ] ;
| { MacroRules }
MacroRules →
MacroRule ( ; MacroRule )* ;?
MacroRule →
MacroMatcher => MacroTranscriber
MacroMatcher →
( MacroMatch* )
| [ MacroMatch* ]
| { MacroMatch* }
MacroMatch →
Tokenexcept $ and delimiters
| MacroMatcher
| $ ( IDENTIFIER_OR_KEYWORDexcept crate | RAW_IDENTIFIER ) : MacroFragSpec
| $ ( MacroMatch+ ) MacroRepSep? MacroRepOp
MacroFragSpec →
block | expr | expr_2021 | ident | item | lifetime | literal
| meta | pat | pat_param | path | stmt | tt | ty | vis
MacroRepSep → Tokenexcept delimiters and MacroRepOp
MacroRepOp → * | + | ?
macro_rules 는 사용자가 선언적인 방식으로 구문 확장을 정의할 수 있도록 합니다. 이러한 확장을 “예제를 통한 매크로” 또는 간단히 “매크로“라고 합니다.
각 예제별 매크로에는 이름과 하나 이상의 규칙 이 있습니다. 각 규칙에는 두 부분이 있습니다. 일치하는 구문을 설명하는 매처 와 성공적으로 일치한 호출을 대체할 구문을 설명하는 트랜스크라이버 입니다. 매처와 트랜스크라이버는 모두 구분 기호로 둘러싸여 있어야 합니다. 매크로는 표현식, 문, 항목(트레이트, impl 및 외부 항목 포함), 유형 또는 패턴으로 확장될 수 있습니다.
트랜스크라이빙
매크로가 호출되면 매크로 확장기는 이름으로 매크로 호출을 조회하고 각 매크로 규칙을 차례로 시도합니다. 첫 번째 성공적인 일치를 트랜스크라이브합니다. 이로 인해 오류가 발생하면 이후 일치는 시도되지 않습니다.
일치시킬 때 미리 보기를 수행하지 않습니다. 컴파일러가 한 번에 하나의 토큰으로 매크로 호출을 구문 분석하는 방법을 명확하게 결정할 수 없으면 오류입니다. 다음 예에서 컴파일러는 다음 토큰이 ’)’인지 확인하기 위해 식별자를 지나 미리 보지 않습니다. 그렇게 하면 호출을 명확하게 구문 분석할 수 있음에도 불구하고 말입니다.
#![allow(unused)]
fn main() {
macro_rules! ambiguity {
($($i:ident)* $j:ident) => { };
}
ambiguity!(error); // 오류: 지역적 모호성
}
매처와 트랜스크라이버 모두에서 ‘$’ 토큰은 매크로 엔진에서 특수 동작을 호출하는 데 사용됩니다(메타변수 및 반복 에서 아래에 설명됨). 이러한 호출의 일부가 아닌 토큰은 한 가지 예외를 제외하고 문자 그대로 일치되고 트랜스크라이브됩니다. 예외는 매처의 외부 구분 기호가 모든 구분 기호 쌍과 일치한다는 것입니다. 따라서 예를 들어 매처 ’(())’는 ’{()}’와 일치하지만 ’{{}}’와는 일치하지 않습니다. ‘$’ 문자는 문자 그대로 일치시키거나 트랜스크라이브할 수 없습니다.
일치된 조각 전달
일치된 조각을 다른 예제별 매크로로 전달할 때 두 번째 매크로의 매처는 조각 유형의 불투명한 AST를 보게 됩니다. 두 번째 매크로는 매처의 조각을 일치시키기 위해 리터럴 토큰을 사용할 수 없으며 동일한 유형의 조각 지정자만 사용할 수 있습니다. ident, lifetime 및 tt 조각 유형은 예외이며 리터럴 토큰으로 일치시킬 수 있습니다. 다음은 이 제한 사항을 보여줍니다.
#![allow(unused)]
fn main() {
macro_rules! foo {
($l:expr) => { bar!($l); }
// 오류: ^^ 매크로 호출에서 이 토큰을 예상한 규칙이 없습니다
}
macro_rules! bar {
(3) => {}
}
foo!(3);
}
다음은 ‘tt’ 조각을 일치시킨 후 토큰을 직접 일치시키는 방법을 보여줍니다.
#![allow(unused)]
fn main() {
// 컴파일 OK
macro_rules! foo {
($l:tt) => { bar!($l); }
}
macro_rules! bar {
(3) => {}
}
foo!(3);
}
메타변수
매처에서 $ 이름 : _조각-지정자 는 지정된 종류의 Rust 구문 조각과 일치하고 메타변수 $ 이름_에 바인딩합니다.
유효한 조각 지정자는 다음과 같습니다.
block: a BlockExpressionexpr: an Expressionexpr_2021: an Expression except UnderscoreExpression and ConstBlockExpression (see macro.decl.meta.edition2024)ident: an IDENTIFIER_OR_KEYWORD except_, RAW_IDENTIFIER, or$crateitem: an Itemlifetime: a LIFETIME_TOKENliteral: matches-?LiteralExpressionmeta: an Attr, the contents of an attributepat: a Pattern (see macro.decl.meta.edition2021)pat_param: a PatternNoTopAltpath: a TypePath style pathstmt: a Statement without the trailing semicolon (except for item statements that require semicolons)tt: a TokenTree (a single token or tokens in matching delimiters(),[], or{})ty: a Typevis: a possibly empty Visibility qualifier
In the transcriber, metavariables are referred to simply by $name, since the fragment kind is specified in the matcher. Metavariables are replaced with the syntax element that matched them. Metavariables can be transcribed more than once or not at all.
The keyword metavariable $crate can be used to refer to the current crate.
2021 Edition differences
Starting with the 2021 edition,
patfragment-specifiers match top-level or-patterns (that is, they accept Pattern).Before the 2021 edition, they match exactly the same fragments as
pat_param(that is, they accept PatternNoTopAlt).관련 에디션은
macro_rules!정의에 적용되는 에디션입니다.
2024 Edition differences
Before the 2024 edition,
exprfragment specifiers do not match UnderscoreExpression or ConstBlockExpression at the top level. They are allowed within subexpressions.The
expr_2021조각 지정자는 2024 이전 에디션과의 하위 호환성을 유지하기 위해 존재합니다.
반복
매처와 트랜스크라이버 모두에서 반복은 반복할 토큰을 $(…) 안에 넣고 반복 연산자를 뒤에 붙여 표시하며, 선택적으로 사이에 구분 기호 토큰을 넣을 수 있습니다.
구분 기호 토큰은 구분 기호나 반복 연산자 중 하나가 아닌 모든 토큰이 될 수 있지만 ; 와 , 가 가장 일반적입니다. 예를 들어, $( $i:ident ),* 는 쉼표로 구분된 임의의 수의 식별자를 나타냅니다. 중첩된 반복은 허용됩니다.
반복 연산자는 다음과 같습니다.
*— 임의의 횟수 반복을 나타냅니다.+— 임의의 횟수이지만 최소 한 번 이상을 나타냅니다.?— 0 또는 1회 발생하는 선택적 조각을 나타냅니다.
Since ? 는 최대 한 번 발생함을 나타내므로 구분 기호와 함께 사용할 수 없습니다.
반복되는 조각은 구분 기호 토큰으로 구분된 지정된 수의 조각과 일치하고 트랜스크라이브됩니다. 메타변수는 해당 조각의 모든 반복과 일치합니다. 예를 들어, 위의 $( $i:ident ),* 예제는 목록의 모든 식별자에 $i 를 일치시킵니다.
트랜스크립션 중에는 컴파일러가 반복을 올바르게 확장하는 방법을 알 수 있도록 반복에 추가 제한 사항이 적용됩니다.
- A metavariable must appear in exactly the same number, kind, and nesting order of repetitions in the transcriber as it did in the matcher. So for the matcher
$( $i:ident ),*, the transcribers=> { $i },=> { $( $( $i )* )* }, and=> { $( $i )+ }are all illegal, but=> { $( $i );* }is correct and replaces a comma-separated list of identifiers with a semicolon-separated list. - 트랜스크라이버의 각 반복에는 확장 횟수를 결정하기 위해 최소한 하나의 메타변수가 포함되어야 합니다. 동일한 반복에 여러 메타변수가 나타나면 동일한 수의 조각에 바인딩되어야 합니다. 예를 들어,
( $( $i:ident ),* ; $( $j:ident ),* ) => (( $( ($i,$j) ),* ))는$j조각과 동일한 수의$i조각을 바인딩해야 합니다. 즉,(a, b, c; d, e, f)로 매크로를 호출하는 것은 합법이며((a,d), (b,e), (c,f))로 확장되지만,(a, b, c; d, e)는 동일한 수가 아니므로 불법입니다. 이 요구 사항은 중첩된 반복의 모든 계층에 적용됩니다.
Scoping, exporting, and importing
역사적인 이유로 예제별 매크로의 범위 지정은 항목처럼 완전히 작동하지 않습니다. 매크로에는 텍스트 범위와 경로 기반 범위의 두 가지 범위 형식이 있습니다. 텍스트 범위는 소스 파일에 항목이 나타나는 순서 또는 여러 파일에 걸쳐 나타나는 순서를 기반으로 하며 기본 범위 지정입니다. 아래에서 자세히 설명합니다. 경로 기반 범위는 항목 범위 지정과 정확히 동일하게 작동합니다. 매크로의 범위 지정, 내보내기 및 가져오기는 주로 속성에 의해 제어됩니다.
매크로가 정규화되지 않은 식별자(다중 부분 경로의 일부가 아님)에 의해 호출되면 먼저 텍스트 범위에서 조회됩니다. 결과가 없으면 경로 기반 범위에서 조회됩니다. 매크로 이름이 경로로 정규화되면 경로 기반 범위에서만 조회됩니다.
use lazy_static::lazy_static; // 경로 기반 가져오기.
macro_rules! lazy_static { // 텍스트 정의.
(lazy) => {};
}
lazy_static!{lazy} // 텍스트 조회는 먼저 우리 매크로를 찾습니다.
self::lazy_static!{} // 경로 기반 조회는 우리 매크로를 무시하고 가져온 매크로를 찾습니다.
Textual scope
텍스트 범위는 주로 소스 파일에 항목이 나타나는 순서를 기반으로 하며 let 으로 선언된 지역 변수의 범위와 유사하게 작동하지만 모듈 수준에서도 적용됩니다. macro_rules! 를 사용하여 매크로를 정의하면 매크로는 정의 후 범위에 들어갑니다(이름은 호출 사이트에서 조회되므로 여전히 재귀적으로 사용할 수 있음). 일반적으로 모듈인 주변 범위가 닫힐 때까지입니다. 이것은 자식 모듈에 들어가거나 여러 파일에 걸쳐 있을 수도 있습니다.
//// src/lib.rs
mod has_macro {
// m!{} // 오류: m이 범위에 없습니다.
macro_rules! m {
() => {};
}
m!{} // OK: m 선언 후에 나타납니다.
mod uses_macro;
}
// m!{} // 오류: m이 범위에 없습니다.
//// src/has_macro/uses_macro.rs
m!{} // OK: src/lib.rs에서 m 선언 후에 나타납니다
매크로를 여러 번 정의하는 것은 오류가 아닙니다. 가장 최근 선언은 범위를 벗어나지 않는 한 이전 선언을 가립니다.
#![allow(unused)]
fn main() {
macro_rules! m {
(1) => {};
}
m!(1);
mod inner {
m!(1);
macro_rules! m {
(2) => {};
}
// m!(1); // 오류: '1'과 일치하는 규칙이 없습니다
m!(2);
macro_rules! m {
(3) => {};
}
m!(3);
}
m!(1);
}
매크로는 함수 내부에서도 로컬로 선언하고 사용할 수 있으며 유사하게 작동합니다.
#![allow(unused)]
fn main() {
fn foo() {
// m!(); // 오류: m이 범위에 없습니다.
macro_rules! m {
() => {};
}
m!();
}
// m!(); // 오류: m이 범위에 없습니다.
}
Textual scope name bindings for macros shadow path-based scope bindings to macros.
#![allow(unused)]
fn main() {
macro_rules! m2 {
() => {
println!("m2");
};
}
// Resolves to path-based candidate from use declaration below.
m!(); // prints "m2\n"
// Introduce second candidate for `m` with textual scope.
//
// This shadows path-based candidate from below for the rest of this
// example.
macro_rules! m {
() => {
println!("m");
};
}
// Introduce `m2` macro as path-based candidate.
//
// This item is in scope for this entire example, not just below the
// use declaration.
use m2 as m;
// Resolves to the textual macro candidate from above the use
// declaration.
m!(); // prints "m\n"
}
Note
For areas where shadowing is not allowed, see name resolution ambiguities.
Path-based scope
By default, a macro has no path-based scope. Macros can gain path-based scope in two ways:
Macros can be re-exported to give them path-based scope from a module other than the crate root.
#![allow(unused)]
fn main() {
mac::m!(); // OK: Path-based lookup finds `m` in the mac module.
mod mac {
// Introduce macro `m` with textual scope.
macro_rules! m {
() => {};
}
// Reexport with path-based scope from within `m`'s textual scope.
pub(crate) use m;
}
}
Macros have an implicit visibility of pub(crate). #[macro_export] changes the implicit visibility to pub.
#![allow(unused)]
fn main() {
// Implicit visibility is `pub(crate)`.
macro_rules! private_m {
() => {};
}
// Implicit visibility is `pub`.
#[macro_export]
macro_rules! pub_m {
() => {};
}
pub(crate) use private_m as private_macro; // OK.
pub use pub_m as pub_macro; // OK.
}
#![allow(unused)]
fn main() {
// Implicit visibility is `pub(crate)`.
macro_rules! private_m {
() => {};
}
// Implicit visibility is `pub`.
#[macro_export]
macro_rules! pub_m {
() => {};
}
pub(crate) use private_m as private_macro; // OK.
pub use pub_m as pub_macro; // OK.
pub use private_m; // ERROR: `private_m` is only public within
// the crate and cannot be re-exported outside.
}
macro_use 속성
The macro_use attribute has two purposes: it may be used on modules to extend the scope of macros defined within them, and it may be used on extern crate to import macros from another crate into the macro_use prelude.
Example
#![allow(unused)] fn main() { #[macro_use] mod inner { macro_rules! m { () => {}; } } m!(); }#[macro_use] extern crate log;
When used on modules, the macro_use attribute uses the MetaWord syntax.
When used on extern crate, it uses the MetaWord and MetaListIdents syntaxes. For more on how these syntaxes may be used, see macro.decl.scope.macro_use.prelude.
The macro_use attribute may be applied to modules or extern crate.
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
The macro_use attribute may not be used on extern crate self.
The macro_use attribute may be used any number of times on a form.
Multiple instances of macro_use in the MetaListIdents syntax may be specified. The union of all specified macros will be imported.
Note
On modules,
rustclints against any MetaWordmacro_useattributes following the first.On
extern crate,rustclints against anymacro_useattributes that have no effect due to not importing any macros not already imported by anothermacro_useattribute. If two or more MetaListIdentsmacro_useattributes import the same macro, the first is linted against. If any MetaWordmacro_useattributes are present, all MetaListIdentsmacro_useattributes are linted against. If two or more MetaWordmacro_useattributes are present, the ones following the first are linted against.
When macro_use is used on a module, the module’s macro scope extends beyond the module’s lexical scope.
Example
#![allow(unused)] fn main() { #[macro_use] mod inner { macro_rules! m { () => {}; } } m!(); // OK }
Specifying macro_use on an extern crate declaration in the crate root imports exported macros from that crate.
Macros imported this way are imported into the macro_use prelude, not textually, which means that they can be shadowed by any other name. Macros imported by macro_use can be used before the import statement.
Note
rustccurrently prefers the last macro imported in case of conflict. Don’t rely on this. This behavior is unusual, as imports in Rust are generally order-independent. This behavior ofmacro_usemay change in the future.For details, see Rust issue #148025.
When using the MetaWord syntax, all exported macros are imported. When using the MetaListIdents syntax, only the specified macros are imported.
Example
#[macro_use(lazy_static)] // Or `#[macro_use]` to import all macros. extern crate lazy_static; lazy_static!{} // self::lazy_static!{} // ERROR: lazy_static is not defined in `self`.
Macros to be imported with macro_use must be exported with macro_export.
The macro_export attribute
The macro_export attribute exports the macro from the crate and makes it available in the root of the crate for path-based resolution.
Example
#![allow(unused)] fn main() { self::m!(); // ^^^^ OK: Path-based lookup finds `m` in the current module. m!(); // As above. mod inner { super::m!(); crate::m!(); } mod mac { #[macro_export] macro_rules! m { () => {}; } } }
The macro_export attribute uses the MetaWord and MetaListIdents syntaxes. With the MetaListIdents syntax, it accepts a single local_inner_macros value.
The macro_export attribute may be applied to macro_rules definitions.
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
Only the first use of macro_export on a macro has effect.
Note
rustclints against any use following the first.
By default, macros only have textual scope and cannot be resolved by path. When the macro_export attribute is used, the macro is made available in the crate root and can be referred to by its path.
Example
Without
macro_export, macros only have textual scope, so path-based resolution of the macro fails.macro_rules! m { () => {}; } self::m!(); // 오류 crate::m!(); // 오류 fn main() {}With
macro_export, path-based resolution works.#[macro_export] macro_rules! m { () => {}; } self::m!(); // OK crate::m!(); // OK fn main() {}
The macro_export attribute causes a macro to be exported from the crate root so that it can be referred to in other crates by path.
Example
Given the following in a
logcrate:#![allow(unused)] fn main() { #[macro_export] macro_rules! warn { ($message:expr) => { eprintln!("WARN: {}", $message) }; } }From another crate, you can refer to the macro by path:
fn main() { log::warn!("example warning"); }
macro_export allows the use of macro_use on an extern crate to import the macro into the macro_use prelude.
Example
Given the following in a
logcrate:#![allow(unused)] fn main() { #[macro_export] macro_rules! warn { ($message:expr) => { eprintln!("WARN: {}", $message) }; } }Using
macro_usein a dependent crate allows you to use the macro from the prelude:#[macro_use] extern crate log; pub mod util { pub fn do_thing() { // Resolved via macro prelude. warn!("example warning"); } }
Adding local_inner_macros to the macro_export attribute causes all single-segment macro invocations in the macro definition to have an implicit $crate:: prefix.
Note
This is intended primarily as a tool to migrate code written before
$cratewas added to the language to work with Rust 2018’s path-based imports of macros. Its use is discouraged in new code.
Example
#![allow(unused)] fn main() { #[macro_export(local_inner_macros)] macro_rules! helped { () => { helper!() } // 자동으로 $crate::helper!()로 변환됩니다. } #[macro_export] macro_rules! helper { () => { () } } }
위생
Macros by example have mixed-site hygiene. This means that loop labels, block labels, and local variables are looked up at the macro definition site while other symbols are looked up at the macro invocation site. For example:
#![allow(unused)]
fn main() {
let x = 1;
fn func() {
unreachable!("이것은 절대 호출되지 않습니다")
}
macro_rules! check {
() => {
assert_eq!(x, 1); // 정의 사이트의 `x` 를 사용합니다.
func(); // 호출 사이트의 `func` 를 사용합니다.
};
}
{
let x = 2;
fn func() { /* 패닉하지 않습니다 */ }
check!();
}
}
매크로 확장에서 정의된 레이블과 지역 변수는 호출 간에 공유되지 않으므로 이 코드는 컴파일되지 않습니다.매크로 확장 시 정의된 레이블과 지역 변수는 호출 간에 공유되지 않으므로 이 코드는 컴파일되지 않습니다:
#![allow(unused)]
fn main() {
macro_rules! m {
(define) => {
let x = 1;
};
(refer) => {
dbg!(x);
};
}
m!(define);
m!(refer);
}
특별한 경우는 $crate 메타변수입니다. 이것은 매크로를 정의하는 크레이트를 참조하며, 호출 사이트에서 범위에 없는 아이템이나 매크로를 조회하기 위해 경로의 시작 부분에서 사용될 수 있습니다.
//// `helper_macro` 크레이트의 정의.
#[macro_export]
macro_rules! helped {
// () => { helper!() } // 'helper'가 범위에 없기 때문에 오류가 발생할 수 있습니다.
// () => { helper!() } // 'helper'가 범위에 없기 때문에 오류가 발생할 수 있습니다.
() => { $crate::helper!() }
}
#[macro_export]
macro_rules! helper {
() => { () }
}
//// 다른 크레이트에서 사용.
// `helper_macro::helper` 는 가져오지 않았습니다!
//// 다른 크레이트에서 사용.
// `helper_macro::helper`는 임포트되지 않았습니다!
use helper_macro::helped;
fn unit() {
helped!();
}
참고: $crate 는 현재 크레이트를 참조하므로 매크로가 아닌 항목을 참조할 때는 정규화된 모듈 경로와 함께 사용해야 합니다.참고로, $crate는 현재 크레이트를 참조하므로, 매크로가 아닌 아이템을 참조할 때는 정규화된 모듈 경로와 함께 사용해야 합니다:
#![allow(unused)]
fn main() {
pub mod inner {
#[macro_export]
macro_rules! call_foo {
() => { $crate::inner::foo() };
}
pub fn foo() {}
}
}
또한 $crate 를 사용하면 매크로가 확장될 때 자체 크레이트 내의 항목을 참조할 수 있지만 가시성에는 영향을 미치지 않습니다. 참조된 항목이나 매크로는 호출 사이트에서 계속 볼 수 있어야 합니다. 다음 예에서 foo() 가 공개되지 않았기 때문에 크레이트 외부에서 call_foo!() 를 호출하려는 모든 시도는 실패합니다.또한, $crate를 사용하면 매크로가 확장될 때 자신의 크레이트 내의 아이템을 참조할 수 있지만, 가시성에는 영향을 미치지 않습니다. 참조된 아이템이나 매크로는 호출 사이트에서 여전히 보여야 합니다. 다음 예제에서, foo()가 공개되지 않았기 때문에 크레이트 외부에서 call_foo!()를 호출하려는 모든 시도는 실패합니다.
#![allow(unused)]
fn main() {
#[macro_export]
macro_rules! call_foo {
() => { $crate::foo() };
}
fn foo() {}
}
Note
Prior to Rust 1.30,
$crateandlocal_inner_macroswere unsupported. They were added alongside path-based imports of macros, to ensure that helper macros did not need to be manually imported by users of a macro-exporting crate. Crates written for earlier versions of Rust that use helper macros need to be modified to use$crateorlocal_inner_macrosto work well with path-based imports.
Follow-set ambiguity restrictions
매크로 시스템에서 사용하는 파서는 상당히 강력하지만 현재 또는 미래 버전의 언어에서 모호성을 방지하기 위해 제한됩니다.매크로 시스템에서 사용하는 파서는 상당히 강력하지만, 현재 또는 미래 버전의 언어에서 모호성을 방지하기 위해 제한됩니다.
특히, 모호한 확장에 대한 규칙 외에도 메타변수와 일치하는 비단말은 해당 종류의 일치 후에 안전하게 사용할 수 있다고 결정된 토큰이 뒤따라야 합니다.특히, 모호한 확장에 대한 규칙 외에도, 메타변수에 의해 일치된 비단말은 해당 종류의 일치 후에 안전하게 사용할 수 있다고 결정된 토큰이 뒤따라야 합니다.
예를 들어, $i:expr [ , ] 와 같은 매크로 매처는 [,] 가 합법적인 표현식의 일부가 될 수 없으므로 구문 분석이 항상 명확하기 때문에 오늘날 Rust에서 이론적으로 허용될 수 있습니다. 그러나 [ 는 후행 표현식을 시작할 수 있으므로 [ 는 표현식 뒤에 오는 것으로 안전하게 배제할 수 있는 문자가 아닙니다. 나중에 Rust 버전에서 [,] 가 허용되면 이 매처는 모호해지거나 잘못 구문 분석되어 작동하는 코드를 손상시킬 수 있습니다. 그러나 , 와 ; 는 합법적인 표현식 구분 기호이므로 $i:expr, 또는 $i:expr; 와 같은 매처는 합법적입니다. 구체적인 규칙은 다음과 같습니다.예를 들어, $i:expr [ , ]와 같은 매크로 매처는 오늘날 Rust에서 이론적으로 받아들여질 수 있습니다. 왜냐하면 [,]는 합법적인 표현식의 일부가 될 수 없으므로 파싱이 항상 명확하기 때문입니다. 그러나 [는 후행 표현식을 시작할 수 있기 때문에, [는 표현식 뒤에 오는 것으로 안전하게 배제할 수 있는 문자가 아닙니다. 만약 [,]가 이후 버전의 Rust에서 허용된다면, 이 매처는 모호해지거나 잘못 파싱되어 작동하는 코드를 깨뜨릴 것입니다. 그러나 ,와 ;는 합법적인 표현식 구분자이므로 $i:expr, 또는 $i:expr;와 같은 매처는 합법적일 것입니다. 구체적인 규칙은 다음과 같습니다:
expr과stmt는=>,,, 또는;중 하나만 뒤에 올 수 있습니다.
pat_param은=>,,,=,|,if또는in중 하나만 뒤따를 수 있습니다.pat_param은=>,,,=,|,if, 또는in중 하나만 뒤에 올 수 있습니다.
pat은=>,,,=,if, 또는in중 하나만 뒤에 올 수 있습니다.
path와ty는=>,,,=,|,;,:,>,>>,[,{,as,where또는block조각 지정자의 매크로 변수 중 하나만 뒤따를 수 있습니다.path와ty는=>,,,=,|,;,:,>,>>,[,{,as,where또는block프래그먼트 지정자의 매크로 변수 중 하나만 뒤에 올 수 있습니다.
vis는,, 비-원시priv가 아닌 식별자, 유형을 시작할 수 있는 모든 토큰 또는ident,ty또는path조각 지정자가 있는 메타변수 중 하나만 뒤따를 수 있습니다.vis는,, 비-원시priv가 아닌 식별자, 타입을 시작할 수 있는 모든 토큰, 또는ident,ty, 또는path프래그먼트 지정자를 가진 메타변수 중 하나만 뒤에 올 수 있습니다.
- 다른 모든 조각 지정자에는 제한이 없습니다.
2021 Edition differences
Before the 2021 edition,
patmay also be followed by|.
반복이 포함된 경우 규칙은 구분 기호를 고려하여 가능한 모든 확장 수에 적용됩니다. 이는 다음을 의미합니다.반복이 포함된 경우, 구분 기호를 고려하여 가능한 모든 확장 수에 규칙이 적용됩니다. 이는 다음을 의미합니다:
- 반복에 구분 기호가 포함된 경우, 해당 구분 기호는 반복의 내용을 따를 수 있어야 합니다.
- 반복이 여러 번 반복될 수 있는 경우(
*또는+), 내용은 스스로를 따를 수 있어야 합니다. - 반복의 내용은 이전에 오는 모든 것을 따를 수 있어야 하며, 뒤에 오는 모든 것은 반복의 내용을 따를 수 있어야 합니다.반복의 내용은 이전에 오는 모든 것을 따를 수 있어야 하며, 뒤에 오는 모든 것은 반복의 내용을 따를 수 있어야 합니다.
- 반복이 0번 일치할 수 있는 경우(
*또는?), 뒤에 오는 모든 것은 이전에 오는 모든 것을 따를 수 있어야 합니다.반복이 0번 일치할 수 있는 경우(*또는?), 뒤에 오는 모든 것은 이전에 오는 모든 것을 따를 수 있어야 합니다.
자세한 내용은 공식 사양 을 참조하십시오.
Procedural macros
절차적 매크로 는 함수의 실행으로 구문 확장을 생성할 수 있습니다. 절차적 매크로는 세 가지 종류 중 하나입니다:
- Function-like macros -
custom!(...) - Derive macros -
#[derive(CustomDerive)] - Attribute macros -
#[CustomAttribute]
절차적 매크로를 사용하면 컴파일 타임에 Rust 구문을 사용하고 생성하는 코드를 실행할 수 있습니다. 절차적 매크로를 AST에서 다른 AST로의 함수로 생각할 수 있습니다.
절차적 매크로는 proc-macro 의 크레이트 타입 을 가진 크레이트의 루트에 정의되어야 합니다. 매크로는 정의된 크레이트에서 사용할 수 없으며, 다른 크레이트에서 임포트될 때만 사용할 수 있습니다.
Note
When using Cargo, Procedural macro crates are defined with the
proc-macrokey in your manifest:[lib] proc-macro = true
함수로서, 그들은 구문을 반환하거나, 패닉하거나, 또는 무한정 반복해야 합니다. 반환된 구문은 절차적 매크로의 종류에 따라 구문을 대체하거나 추가합니다. 패닉은 컴파일러에 의해 잡혀 컴파일러 오류로 바뀝니다. 무한 루프는 컴파일러에 의해 잡히지 않아 컴파일러를 멈추게 합니다.
절차적 매크로는 컴파일 중에 실행되므로 컴파일러와 동일한 리소스를 갖습니다. 예를 들어, 표준 입력, 오류 및 출력은 컴파일러가 액세스할 수 있는 것과 동일합니다. 마찬가지로 파일 액세스도 동일합니다. 이 때문에 절차적 매크로는 Cargo의 빌드 스크립트 와 동일한 보안 문제를 가지고 있습니다.
절차적 매크로는 오류를 보고하는 두 가지 방법이 있습니다. 첫 번째는 패닉하는 것입니다. 두 번째는 compile_error 매크로 호출을 내보내는 것입니다.
proc_macro 크레이트
절차적 매크로 크레이트는 거의 항상 컴파일러에서 제공하는 proc_macro 크레이트에 링크됩니다. proc_macro 크레이트는 절차적 매크로를 작성하는 데 필요한 유형과 이를 더 쉽게 만드는 기능을 제공합니다.
이 크레이트는 주로 TokenStream 타입을 포함합니다. 절차적 매크로는 AST 노드 대신 토큰 스트림 을 통해 작동하며, 이는 컴파일러와 절차적 매크로 모두에게 시간이 지나도 훨씬 더 안정적인 인터페이스입니다. 토큰 스트림 은 대략 Vec<TokenTree> 와 동일하며, 여기서 TokenTree 는 어휘 토큰으로 생각할 수 있습니다. 예를 들어 foo 는 Ident 토큰이고, . 는 Punct 토큰이며, 1.2 는 Literal 토큰입니다. TokenStream 타입은 Vec<TokenTree> 와 달리 복제 비용이 저렴합니다.
모든 토큰에는 연관된 Span 이 있습니다. Span 은 수정할 수 없지만 제조할 수 있는 불투명한 값입니다. Span 은 프로그램 내 소스 코드의 범위를 나타내며 주로 오류 보고에 사용됩니다. Span 자체를 수정할 수는 없지만, 다른 토큰에서 Span 을 가져오는 것과 같이 모든 토큰과 연관된 Span 은 언제든지 변경할 수 있습니다.
절차적 매크로 위생
절차적 매크로는 비위생적 입니다. 이는 출력 토큰 스트림이 바로 옆 코드에 인라인으로 작성된 것처럼 동작함을 의미합니다. 이는 외부 항목의 영향을 받고 외부 임포트에도 영향을 미친다는 것을 의미합니다.
매크로 작성자는 이 제한 사항을 감안할 때 가능한 한 많은 컨텍스트에서 매크로가 작동하도록 주의해야 합니다. 여기에는 종종 라이브러리의 아이템에 대한 절대 경로를 사용하거나(예: Option 대신 ::std::option::Option) 생성된 함수가 다른 함수와 충돌할 가능성이 없는 이름을 갖도록 하는 것(예: foo 대신 __internal_foo)이 포함됩니다.
The proc_macro attribute
The proc_macro attribute defines a function-like procedural macro.
Example
This macro definition ignores its input and emits a function
answerinto its scope.#![crate_type = "proc-macro"] extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro] pub fn make_answer(_item: TokenStream) -> TokenStream { "fn answer() -> u32 { 42 }".parse().unwrap() }We can use it in a binary crate to print “42” to standard output.
extern crate proc_macro_examples; use proc_macro_examples::make_answer; make_answer!(); fn main() { println!("{}", answer()); }
The proc_macro attribute uses the MetaWord syntax.
The proc_macro attribute may only be applied to a pub function of type fn(TokenStream) -> TokenStream where TokenStream comes from the proc_macro crate. It must have the “Rust” ABI. No other function qualifiers are allowed. It must be located in the root of the crate.
The proc_macro attribute may only be specified once on a function.
The proc_macro attribute publicly defines the macro in the macro namespace in the root of the crate with the same name as the function.
A function-like macro invocation of a function-like procedural macro will pass what is inside the delimiters of the macro invocation as the input TokenStream argument and replace the entire macro invocation with the output TokenStream of the function.
Function-like procedural macros may be invoked in any macro invocation position, which includes:
- Statements
- Expressions
- 패턴
- Type expressions
- Item positions, including items in
externblocks - Inherent and trait implementations
- Trait definitions
The proc_macro_derive attribute
Applying the proc_macro_derive attribute to a function defines a derive macro that can be invoked by the derive attribute. These macros are given the token stream of a struct, enum, or union definition and can emit new items after it. They can also declare and use derive macro helper attributes.
Example
This derive macro ignores its input and appends tokens that define a function.
#![crate_type = "proc-macro"] extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_derive(AnswerFn)] pub fn derive_answer_fn(_item: TokenStream) -> TokenStream { "fn answer() -> u32 { 42 }".parse().unwrap() }To use it, we might write:
extern crate proc_macro_examples; use proc_macro_examples::AnswerFn; #[derive(AnswerFn)] struct Struct; fn main() { assert_eq!(42, answer()); }
The syntax for the proc_macro_derive attribute is:
Syntax
ProcMacroDeriveAttribute →
proc_macro_derive ( DeriveMacroName ( , DeriveMacroAttributes )? ,? )
DeriveMacroAttributes →
attributes ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )
The name of the derive macro is given by DeriveMacroName. The optional attributes argument is described in macro.proc.derive.attributes.
The proc_macro_derive attribute may only be applied to a pub function with the Rust ABI defined in the root of the crate with a type of fn(TokenStream) -> TokenStream where TokenStream comes from the proc_macro crate. The function may be const and may use extern to explicitly specify the Rust ABI, but it may not use any other qualifiers (e.g. it may not be async or unsafe).
The proc_macro_derive attribute may be used only once on a function.
The proc_macro_derive attribute publicly defines the derive macro in the macro namespace in the root of the crate.
The input TokenStream is the token stream of the item to which the derive attribute is applied. The output TokenStream must be a (possibly empty) set of items. These items are appended following the input item within the same module or block.
파생 매크로 헬퍼 속성
Derive macros can declare derive macro helper attributes to be used within the scope of the item to which the derive macro is applied. These attributes are inert. While their purpose is to be used by the macro that declared them, they can be seen by any macro.
A helper attribute for a derive macro is declared by adding its identifier to the attributes list in the proc_macro_derive attribute.
Example
This declares a helper attribute and then ignores it.
#![crate_type="proc-macro"] extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_derive(WithHelperAttr, attributes(helper))] pub fn derive_with_helper_attr(_item: TokenStream) -> TokenStream { TokenStream::new() }To use it, we might write:
#[derive(WithHelperAttr)] struct Struct { #[helper] field: (), }
When a derive macro invocation is applied to an item, the helper attributes introduced by that derive macro become in scope 1) for attributes that are applied to that item and are applied lexically after the derive macro invocation and 2) for attributes that are applied to fields and variants inside of the item.
Note
rustc currently allows derive helpers to be used before the macro that introduces them. Such derive helpers used out of order may not shadow other attribute macros. This behavior is deprecated and slated for removal.
#[helper] // Deprecated, hard error in the future. #[derive(WithHelperAttr)] struct Struct { field: (), }For more details, see Rust issue #79202.
The proc_macro_attribute attribute
The proc_macro_attribute attribute defines an attribute macro which can be used as an outer attribute.
Example
This attribute macro takes the input stream and emits it as-is, effectively being a no-op attribute.
#![crate_type = "proc-macro"] extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_attribute] pub fn return_as_is(_attr: TokenStream, item: TokenStream) -> TokenStream { item }
Example
This shows, in the output of the compiler, the stringified
TokenStreams that attribute macros see.// my-macro/src/lib.rs extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_attribute] pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream { println!("attr: \"{attr}\""); println!("item: \"{item}\""); item }// src/lib.rs extern crate my_macro; use my_macro::show_streams; // Example: Basic function. #[show_streams] fn invoke1() {} // out: attr: "" // out: item: "fn invoke1() {}" // Example: Attribute with input. #[show_streams(bar)] fn invoke2() {} // out: attr: "bar" // out: item: "fn invoke2() {}" // Example: Multiple tokens in the input. #[show_streams(multiple => tokens)] fn invoke3() {} // out: attr: "multiple => tokens" // out: item: "fn invoke3() {}" // Example: Delimiters in the input. #[show_streams { delimiters }] fn invoke4() {} // out: attr: "delimiters" // out: item: "fn invoke4() {}"
The proc_macro_attribute attribute uses the MetaWord syntax.
The proc_macro_attribute attribute may only be applied to a pub function of type fn(TokenStream, TokenStream) -> TokenStream where TokenStream comes from the proc_macro crate. It must have the “Rust” ABI. No other function qualifiers are allowed. It must be located in the root of the crate.
The proc_macro_attribute attribute may only be specified once on a function.
The proc_macro_attribute attribute defines the attribute in the macro namespace in the root of the crate with the same name as the function.
Attribute macros can only be used on:
- 아이템
- Items in
externblocks - Inherent and trait implementations
- Trait definitions
The first TokenStream parameter is the delimited token tree following the attribute’s name but not including the outer delimiters. If the applied attribute contains only the attribute name or the attribute name followed by empty delimiters, the TokenStream is empty.
The second TokenStream is the rest of the item, including other attributes on the item.
The item to which the attribute is applied is replaced by the zero or more items in the returned TokenStream.
선언적 매크로 토큰과 절차적 매크로 토큰
선언적 macro_rules 매크로와 절차적 매크로는 토큰(또는 TokenTrees)에 대해 비슷하지만 다른 정의를 사용합니다.
macro_rules 의 토큰 트리(tt 매처에 해당)는 다음과 같이 정의됩니다.
- 구분된 그룹 (
(...),{...}등) - 언어에서 지원하는 모든 연산자, 단일 문자 및 다중 문자 연산자 모두 포함(
+,+=).- 이 집합에는 작은따옴표
'가 포함되지 않음에 유의하세요.
- 이 집합에는 작은따옴표
- 리터럴 (
"string",1등)- 부정(예:
-1)은 절대 리터럴 토큰의 일부가 아니며, 별도의 연산자 토큰임에 유의하세요.
- 부정(예:
- 키워드를 포함한 식별자 (
ident,r#ident,fn) - 라이프타임 (
'ident) macro_rules의 메타변수 치환 (예:mac의 확장 후macro_rules! mac { ($my_expr: expr) => { $my_expr } }에서의$my_expr. 전달된 표현식과 관계없이 단일 토큰 트리로 간주됨)
절차적 매크로에서 토큰 트리는 다음과 같이 정의됩니다
- 구분된 그룹 (
(...),{...}등) - 언어에서 지원하는 연산자에 사용되는 모든 구두점 문자(
+, 단+=는 아님) 및 작은따옴표'문자(주로 라이프타임에 사용됨. 라이프타임 분리 및 결합 동작은 아래 참조) - 리터럴 (
"string",1등)- 부정(예:
-1)은 정수 및 부동 소수점 리터럴의 일부로 지원됩니다.
- 부정(예:
- 키워드를 포함한 식별자 (
ident,r#ident,fn)
Mismatches between these two definitions are accounted for when token streams are passed to and from procedural macros. Note that the conversions below may happen lazily, so they might not happen if the tokens are not actually inspected.
절차적 매크로로 전달될 때
- 모든 다중 문자 연산자는 단일 문자로 분리됩니다.
- 라이프타임은
'문자와 식별자로 분리됩니다. - The keyword metavariable
$crateis passed as a single identifier. - All other metavariable substitutions are represented as their underlying token streams.
- 이러한 토큰 스트림은 파싱 우선순위를 보존하기 위해 필요한 경우 암시적 구분자(
Delimiter::None)를 가진 구분된 그룹(Group)으로 래핑될 수 있습니다. tt및ident치환은 절대 이러한 그룹으로 래핑되지 않으며 항상 기본 토큰 트리로 표현됩니다.
- 이러한 토큰 스트림은 파싱 우선순위를 보존하기 위해 필요한 경우 암시적 구분자(
절차적 매크로에서 방출될 때
- 구두점 문자는 가능한 경우 다중 문자 연산자로 결합됩니다.
- 식별자와 결합된 작은따옴표
'는 라이프타임으로 결합됩니다. - 음수 리터럴은 두 개의 토큰(
-와 리터럴)으로 변환되며, 파싱 우선순위를 보존하기 위해 필요한 경우 암시적 구분자(Delimiter::None)를 가진 구분된 그룹(Group)으로 래핑될 수 있습니다.
선언적 매크로와 절차적 매크로 모두 문서 주석 토큰(예: /// Doc)을 지원하지 않으므로, 매크로로 전달될 때는 항상 이에 상응하는 #[doc = r"str"] 속성을 나타내는 토큰 스트림으로 변환된다는 점에 유의하세요.
크레이트와 소스 파일
Syntax
Crate →
InnerAttribute*
Item*
Note
Although Rust, like any other language, can be implemented by an interpreter as well as a compiler, the only existing implementation is a compiler, and the language has always been designed to be compiled. For these reasons, this section assumes a compiler.
러스트의 시맨틱은 컴파일 타임과 런타임 사이의 단계 구분 을 따릅니다.1 정적 해석 을 갖는 시맨틱 규칙은 컴파일의 성공 여부를 결정하며, 동적 해석 을 갖는 시맨틱 규칙은 런타임에서의 프로그램 동작을 결정합니다.
컴파일 모델은 크레이트 라고 불리는 결과물을 중심으로 합니다. 각 컴파일 과정은 소스 형태의 단일 크레이트를 처리하며, 성공하면 실행 파일이나 라이브러리 형태의 단일 바이너리 크레이트를 생성합니다.2
크레이트 는 컴파일과 링크의 단위일 뿐만 아니라 버전 관리, 배포 및 런타임 로딩의 단위이기도 합니다. 크레이트는 중첩된 모듈 스코프의 트리 를 포함합니다. 이 트리의 최상위 레벨은 (모듈 내 경로의 관점에서 볼 때) 익명 모듈이며, 크레이트 내의 모든 아이템은 크레이트의 모듈 트리 내 위치를 나타내는 정규 모듈 경로 를 가집니다.
러스트 컴파일러는 항상 단일 소스 파일을 입력으로 호출되며, 항상 단일 출력 크레이트를 생성합니다. 해당 소스 파일을 처리하는 과정에서 다른 소스 파일이 모듈로 로드될 수 있습니다. 소스 파일의 확장자는 .rs 입니다.
A Rust source file describes a module, the name and location of which — in the module tree of the current crate — are defined from outside the source file: either by an explicit Module item in a referencing source file, or by the name of the crate itself.
모든 소스 파일은 모듈이지만, 모든 모듈이 별도의 소스 파일을 가질 필요는 없습니다. 모듈 정의 는 하나의 파일 내에 중첩될 수 있습니다.
Each source file contains a sequence of zero or more Item definitions, and may optionally begin with any number of attributes that apply to the containing module, most of which influence the behavior of the compiler.
익명 크레이트 모듈은 크레이트 전체에 적용되는 추가 속성을 가질 수 있습니다.
Note
The file’s contents may be preceded by a shebang.
#![allow(unused)]
fn main() {
// 크레이트 이름을 지정합니다.
#![crate_name = "projx"]
// 출력 아티팩트의 타입을 지정합니다.
#![crate_type = "lib"]
// 경고를 활성화합니다.
// 이 작업은 익명 크레이트 모듈뿐만 아니라 모든 모듈에서 수행할 수 있습니다.
#![warn(non_camel_case_types)]
}
Main functions
main 함수 를 포함하는 크레이트는 실행 파일로 컴파일될 수 있습니다.
main 함수가 존재하는 경우, 인자를 받지 않아야 하며, 어떠한 트레잇 또는 라이프타임 바운드 도 선언하지 않아야 하고, where 절 을 가져서는 안 되며, 반환 타입은 Termination 트레잇을 구현해야 합니다.
fn main() {}
fn main() -> ! {
std::process::exit(0);
}
fn main() -> impl std::process::Termination {
std::process::ExitCode::SUCCESS
}
main 함수는 외부 크레이트나 현재 크레이트로부터 임포트된 것일 수 있습니다.
#![allow(unused)]
fn main() {
mod foo {
pub fn bar() {
println!("Hello, world!");
}
}
use foo::bar as main;
}
Note
Types with implementations of
Terminationin the standard library include:
()!InfallibleExitCodeResult<T, E> where T: Termination, E: Debug
Uncaught foreign unwinding
When a “foreign” unwind (e.g. an exception thrown from C++ code, or a panic! in Rust code using a different panic handler) propagates beyond the main function, the process will be safely terminated. This may take the form of an abort, in which case it is not guaranteed that any Drop calls will be executed, and the error output may be less informative than if the runtime had been terminated by a “native” Rust panic.
For more information, see the panic documentation.
no_main 속성
The no_main 속성 은 실행 가능한 바이너리에 대해 main 기호 내보내기를 비활성화하기 위해 크레이트 수준에서 적용될 수 있습니다. 이것은 연결되는 다른 일부 개체가 main 을 정의할 때 유용합니다.
crate_name 속성
The crate_name attribute may be applied at the crate level to specify the name of the crate with the MetaNameValueStr syntax.
#![allow(unused)]
#![crate_name = "mycrate"]
fn main() {
}
크레이트 이름은 비어 있으면 안 되며, 유니코드 영숫자 또는 _ (U+005F) 문자만 포함해야 합니다.
-
이러한 구별은 인터프리터에도 존재합니다. 구문 분석, 유형 검사 및 린트와 같은 정적 검사는 프로그램이 실행되는 시점과 관계없이 프로그램이 실행되기 전에 수행되어야 합니다. ↩
-
크레이트는 ECMA-335 CLI 모델의 어셈블리, SML/NJ 컴파일 관리자의 라이브러리, Owens 및 Flatt 모듈 시스템의 단위 또는 Mesa의 구성 과 다소 유사합니다. ↩
조건부 컴파일
Syntax
ConfigurationPredicate →
ConfigurationOption
| ConfigurationAll
| ConfigurationAny
| ConfigurationNot
| true
| false
ConfigurationOption →
IDENTIFIER ( = ( STRING_LITERAL | RAW_STRING_LITERAL ) )?
ConfigurationAll →
all ( ConfigurationPredicateList? )
ConfigurationAny →
any ( ConfigurationPredicateList? )
ConfigurationNot →
not ( ConfigurationPredicate )
ConfigurationPredicateList →
ConfigurationPredicate ( , ConfigurationPredicate )* ,?
조건부로 컴파일된 소스 코드 는 특정 조건에서만 컴파일되는 소스 코드입니다.
Source code can be made conditionally compiled using the cfg and cfg_attr attributes and the built-in cfg! and cfg_select! macros.
컴파일 여부는 컴파일된 크레이트의 대상 아키텍처, 컴파일러에 전달된 임의의 값 및 아래에 자세히 설명된 기타 사항에 따라 달라질 수 있습니다.
각 형태의 조건부 컴파일은 참 또는 거짓으로 평가되는 구성 술어 를 사용합니다. 술어는 다음 중 하나입니다.
- 구성 옵션. 옵션이 설정되면 술어는 참이고 설정되지 않으면 거짓입니다.
all()은 쉼표로 구분된 구성 술어 목록과 함께 사용됩니다. 주어진 모든 술어가 참이거나 목록이 비어 있으면 참입니다.
any()은 쉼표로 구분된 구성 술어 목록과 함께 사용됩니다. 주어진 술어 중 하나 이상이 참이면 참입니다. 술어가 없으면 거짓입니다.
not()은 구성 술어와 함께 사용됩니다. 술어가 거짓이면 참이고 술어가 참이면 거짓입니다.
trueorfalseliterals, which are always true or false respectively.
구성 옵션 은 이름 또는 키-값 쌍이며 설정되거나 설정되지 않습니다.
이름은 unix 와 같이 단일 식별자로 작성됩니다.
키-값 쌍은 식별자, =, 그리고 문자열로 작성됩니다(예: target_arch = "x86_64").
Note
Whitespace around the
=is ignored, sofoo="bar"andfoo = "bar"are equivalent.
키는 고유할 필요가 없습니다. 예를 들어, feature = "std" 와 feature = "serde" 를 동시에 설정할 수 있습니다.
Set configuration options
어떤 구성 옵션이 설정되는지는 크레이트 컴파일 중에 정적으로 결정됩니다.
일부 옵션은 컴파일에 대한 데이터를 기반으로 컴파일러 설정 됩니다.
다른 옵션은 코드 외부에서 컴파일러에 전달된 입력을 기반으로 임의로 설정 됩니다.
컴파일 중인 크레이트의 소스 코드 내에서 구성 옵션을 설정할 수 없습니다.
Note
For
rustc, arbitrary-set configuration options are set using the--cfgflag. Configuration values for a specified target can be displayed withrustc --print cfg --target $TARGET.
Note
Configuration options with the key
featureare a convention used by Cargo for specifying compile-time options and optional dependencies.
target_arch
키-값 옵션은 대상의 CPU 아키텍처로 한 번 설정됩니다. 값은 플랫폼의 대상 트리플의 첫 번째 요소와 유사하지만 동일하지는 않습니다.
예제 값:
"x86""x86_64""mips""powerpc""powerpc64""arm""aarch64"
target_feature
현재 컴파일 대상에 사용할 수 있는 각 플랫폼 기능에 대해 설정된 키-값 옵션입니다.
예제 값:
"avx""avx2""crt-static""rdrand""sse""sse2""sse4.1"
사용 가능한 기능에 대한 자세한 내용은 target_feature 속성 을 참조하십시오.
An additional feature of crt-static is available to the target_feature option to indicate that a static C runtime is available.
target_os
키-값 옵션은 대상의 운영 체제로 한 번 설정됩니다. 이 값은 플랫폼의 대상 트리플의 두 번째 및 세 번째 요소와 유사합니다.
예제 값:
"windows""macos""ios""linux""android""freebsd""dragonfly""openbsd""netbsd""none"(임베디드 대상의 경우 일반적)
target_family
키-값 옵션은 대상이 일반적으로 속하는 운영 체제 또는 아키텍처 제품군과 같이 대상에 대한 보다 일반적인 설명을 제공합니다. target_family 키-값 쌍은 여러 개 설정할 수 있습니다.
예제 값:
"unix""windows""wasm""unix"와"wasm"모두
unix 및 windows
unix 는 target_family = "unix" 가 설정되면 설정됩니다.
windows 는 target_family = "windows" 가 설정되면 설정됩니다.
target_env
사용된 ABI 또는 libc 에 대한 정보와 함께 대상 플랫폼에 대한 추가 명확화 정보로 설정된 키-값 옵션입니다. 역사적인 이유로 이 값은 실제로 명확화가 필요할 때만 빈 문자열이 아닌 것으로 정의됩니다. 따라서 예를 들어 많은 GNU 플랫폼에서 이 값은 비어 있습니다. 이 값은 플랫폼의 대상 트리플의 네 번째 요소와 유사합니다. 한 가지 차이점은 gnueabihf 와 같은 임베디드 ABI는 단순히 target_env 를 "gnu" 로 정의한다는 것입니다.
예제 값:
"""gnu""msvc""musl""sgx""sim""macabi"
target_abi
Key-value option set to further disambiguate the target with information about the target ABI.
역사적인 이유로 이 값은 실제로 명확화가 필요할 때만 빈 문자열이 아닌 것으로 정의됩니다. 따라서 예를 들어 많은 GNU 플랫폼에서 이 값은 비어 있습니다.
예제 값:
"""llvm""eabihf""abi64"
target_endian
키-값 옵션은 대상의 CPU 엔디안에 따라 “little” 또는 “big” 값으로 한 번 설정됩니다.
target_pointer_width
키-값 옵션은 대상의 포인터 너비를 비트 단위로 한 번 설정합니다.
예제 값:
"16""32""64"
target_vendor
키-값 옵션은 대상의 공급업체로 한 번 설정됩니다.
예제 값:
"apple""fortanix""pc""unknown"
target_has_atomic
대상이 원자적 로드, 저장 및 비교-스왑 작업을 지원하는 각 비트 너비에 대해 설정된 키-값 옵션입니다.
이 cfg가 있으면 관련 원자 너비에 대해 안정적인 core::sync::atomic API를 모두 사용할 수 있습니다.
가능한 값:
"8""16""32""64""128""ptr"
test
테스트 하네스를 컴파일할 때 활성화됩니다. rustc 로 --test 플래그를 사용하여 수행됩니다. 테스트 지원에 대한 자세한 내용은 테스팅 을 참조하십시오.
debug_assertions
최적화 없이 컴파일할 때 기본적으로 활성화됩니다. 이는 프로덕션이 아닌 개발 환경에서 추가 디버깅 코드를 활성화하는 데 사용할 수 있습니다. 예를 들어, 표준 라이브러리의 debug_assert! 매크로의 동작을 제어합니다.
proc_macro
컴파일 중인 크레이트가 proc_macro 크레이트 타입 으로 컴파일될 때 설정됩니다.
panic
Key-value option set depending on the panic strategy. Note that more values may be added in the future.
예제 값:
"abort""unwind"
조건부 컴파일의 형태
cfg 속성
The cfg attribute conditionally includes the form to which it is attached based on a configuration predicate.
Example
#![allow(unused)] fn main() { // 이 함수는 macOS용으로 컴파일할 때만 빌드에 포함됩니다. #[cfg(target_os = "macos")] fn macos_only() { // ... } // 이 함수는 foo 또는 bar 중 하나가 정의된 경우에만 포함됩니다. #[cfg(any(foo, bar))] fn needs_foo_or_bar() { // ... } // This function is only included when compiling for a unixish OS with a 32-bit // architecture #[cfg(all(unix, target_pointer_width = "32"))] fn on_32bit_unix() { // ... } // 이 함수는 foo가 정의되지 않은 경우에만 포함됩니다. #[cfg(not(foo))] fn needs_not_foo() { // ... } // 이 함수는 패닉 전략이 unwind로 설정된 경우에만 포함됩니다. #[cfg(panic = "unwind")] fn when_unwinding() { // ... } }
The syntax for the cfg attribute is:
Syntax
CfgAttribute → cfg ( ConfigurationPredicate )
The cfg attribute may be used anywhere attributes are allowed.
The cfg attribute may be used any number of times on a form. The form to which the attributes are attached will not be included if any of the cfg predicates are false except as described in cfg.attr.crate-level-attrs.
If the predicates are true, the form is rewritten to not have the cfg attributes on it. If any predicate is false, the form is removed from the source code.
When a crate-level cfg has a false predicate, the crate itself still exists. Any crate attributes preceding the cfg are kept, and any crate attributes following the cfg are removed as well as removing all of the following crate contents.
Example
The behavior of not removing the preceding attributes allows you to do things such as include
#![no_std]to avoid linkingstdeven if a#![cfg(...)]has otherwise removed the contents of the crate. For example:// This `no_std` attribute is kept even though the crate-level `cfg` // attribute is false. #![no_std] #![cfg(false)] // This function is not included. pub fn example() {}
cfg_attr 속성
The cfg_attr attribute conditionally includes attributes based on a configuration predicate.
Example
The following module will either be found at
linux.rsorwindows.rsbased on the target.#[cfg_attr(target_os = "linux", path = "linux.rs")] #[cfg_attr(windows, path = "windows.rs")] mod os;
The syntax for the cfg_attr attribute is:
Syntax
CfgAttrAttribute → cfg_attr ( ConfigurationPredicate , CfgAttrs? )
The cfg_attr attribute may be used anywhere attributes are allowed.
The cfg_attr attribute may be used any number of times on a form.
crate_type 및 crate_name 속성은 cfg_attr 과 함께 사용할 수 없습니다.
When the configuration predicate is true, cfg_attr expands out to the attributes listed after the predicate.
Zero, one, or more attributes may be listed. Multiple attributes will each be expanded into separate attributes.
Example
#[cfg_attr(feature = "magic", sparkles, crackles)] fn bewitched() {} // `magic` 피처 플래그가 활성화되면, 위 코드는 다음과 같이 확장됩니다: #[sparkles] #[crackles] fn bewitched() {}
Note
The
cfg_attrcan expand to anothercfg_attr. For example,#[cfg_attr(target_os = "linux", cfg_attr(feature = "multithreaded", some_other_attribute))]is valid. This example would be equivalent to#[cfg_attr(all(target_os = "linux", feature = "multithreaded"), some_other_attribute)].
cfg 매크로
내장된 cfg 매크로는 단일 구성 조건자를 취하며, 조건자가 참이면 true 리터럴로, 거짓이면 false 리터럴로 평가됩니다.
예:
#![allow(unused)]
fn main() {
let machine_kind = if cfg!(unix) {
"unix"
} else if cfg!(windows) {
"windows"
} else {
"unknown"
};
println!("저는 {} 머신에서 실행 중입니다!", machine_kind);
}
The cfg_select macro
The built-in cfg_select! macro can be used to select code at compile-time based on multiple configuration predicates.
Example
#![allow(unused)] fn main() { cfg_select! { unix => { fn foo() { /* unix specific functionality */ } } target_pointer_width = "32" => { fn foo() { /* non-unix, 32-bit functionality */ } } _ => { fn foo() { /* fallback implementation */ } } } let is_unix_str = cfg_select! { unix => "unix", _ => "not unix", }; }
Syntax
CfgSelect → CfgSelectArms?
CfgSelectArms →
CfgSelectConfigurationPredicate =>
(
{ ^ TokenTree } ,? CfgSelectArms?
| ExpressionWithBlockNoAttrs ,? CfgSelectArms?
| ExpressionWithoutBlockNoAttrs ( , CfgSelectArms? )?
)
CfgSelectConfigurationPredicate →
ConfigurationPredicate | _
cfg_select expands to the payload of the first arm whose configuration predicate evaluates to true.
If the entire payload is wrapped in curly braces, the braces are removed during expansion.
The configuration predicate _ always evaluates to true.
It is a compile error if none of the predicates evaluate to true.
Each right-hand side must be a syntactically valid expansion for the position in which the macro is invoked.
아이템
Syntax
Item →
OuterAttribute* ( VisItem | MacroItem )
VisItem →
Visibility?
(
Module
| ExternCrate
| UseDeclaration
| Function
| TypeAlias
| Struct
| Enumeration
| Union
| ConstantItem
| StaticItem
| Trait
| Implementation
| ExternBlock
)
아이템 은 크레이트의 구성 요소입니다. 아이템은 중첩된 모듈 집합으로 크레이트 내에 구성됩니다. 모든 크레이트는 단일 “최상위” 익명 모듈을 가지며, 크레이트 내의 모든 추가 아이템은 크레이트의 모듈 트리 내에서 경로 를 가집니다.
아이템은 컴파일 타임에 완전히 결정되며, 실행 중에 일반적으로 고정되어 있고, 읽기 전용 메모리에 상주할 수 있습니다.
아이템에는 여러 종류가 있습니다:
- 모듈
extern crate선언use선언- 함수 정의
- type alias definitions
- 구조체 정의
- 열거형 정의
- 공용체 정의
- 상수 아이템
- 정적 아이템
- 트레잇 정의
- 구현
extern블록
아이템은 크레이트 루트, 모듈, 또는 블록 표현식 에서 선언될 수 있습니다.
연관 아이템 이라고 불리는 아이템의 일부는 트레잇 과 구현 에서 선언될 수 있습니다.
외부 아이템이라고 불리는 아이템의 일부는 extern 블록 에서 선언될 수 있습니다.
아이템은 임의의 순서로 정의될 수 있으나, 자체적인 스코프 동작을 갖는 macro_rules 는 예외입니다.
아이템 이름의 이름 확인 을 통해 아이템은 모듈이나 블록 내에서 참조되는 지점의 앞이나 뒤에서 정의될 수 있습니다.
아이템의 스코프 규칙에 대한 정보는 아이템 스코프 를 참조하십시오.
모듈
Syntax
Module →
unsafe? mod IDENTIFIER ;
| unsafe? mod IDENTIFIER {
InnerAttribute*
Item*
}
모듈은 0개 이상의 아이템 을 담는 컨테이너입니다.
모듈 아이템 은 중괄호로 둘러싸여 있고, 이름이 지정되며, mod 키워드가 앞에 붙은 모듈입니다. 모듈 아이템은 크레이트를 구성하는 모듈 트리에 이름이 지정된 새로운 모듈을 도입합니다.
모듈은 임의로 중첩될 수 있습니다.
모듈의 예:
#![allow(unused)]
fn main() {
mod math {
type Complex = (f64, f64);
fn sin(f: f64) -> f64 {
/* ... */
unimplemented!();
}
fn cos(f: f64) -> f64 {
/* ... */
unimplemented!();
}
fn tan(f: f64) -> f64 {
/* ... */
unimplemented!();
}
}
}
모듈은 해당 모듈이나 블록이 위치한 타입 네임스페이스 에 정의됩니다.
모듈 내 같은 네임스페이스에 같은 이름을 가진 아이템을 여러 개 정의하는 것은 오류입니다. 제약 사항 및 가려짐(shadowing) 동작에 대한 자세한 내용은 스코프 챕터 를 참조하십시오.
unsafe 키워드는 구문적으로 mod 키워드 앞에 나타나는 것이 허용되지만, 시맨틱 수준에서는 거부됩니다. 이는 매크로가 해당 구문을 소비하고 unsafe 키워드를 토큰 스트림에서 제거하기 전에 이를 사용할 수 있게 하기 위함입니다.
Module source filenames
본문이 없는 모듈은 외부 파일에서 로드됩니다. 모듈에 path 속성이 없는 경우, 파일 경로는 논리적 모듈 경로 를 반영합니다.
상위 모듈 경로 구성 요소는 디렉터리이며, 모듈의 내용은 모듈 이름에 .rs 확장자를 더한 파일에 있습니다. 예를 들어, 다음 모듈 구조는 이에 해당하는 파일 시스템 구조를 가질 수 있습니다:
| 모듈 경로 | 파일시스템 경로 | 파일 내용 |
|---|---|---|
crate | lib.rs | mod util; |
crate::util | util.rs | mod config; |
crate::util::config | util/config.rs |
모듈 파일 이름은 해당 디렉토리 내의 mod.rs 라는 이름의 파일에 내용이 있는 디렉토리로서의 모듈 이름일 수도 있습니다. 위의 예시는 crate::util 의 내용을 util/mod.rs 라는 이름의 파일로 표현할 수도 있습니다. util.rs 와 util/mod.rs 를 동시에 가질 수는 없습니다.
Note
Prior to
rustc1.30, usingmod.rsfiles was the way to load a module with nested children. It is encouraged to use the new naming convention as it is more consistent, and avoids having many files namedmod.rswithin a project.
path 속성
외부 파일 모듈을 로드하는 데 사용되는 디렉토리와 파일은 path 속성의 영향을 받을 수 있습니다.
인라인 모듈 블록 내에 있지 않은 모듈의 path 속성의 경우, 파일 경로는 소스 파일이 위치한 디렉토리에 상대적입니다. 예를 들어, 다음 코드 스니펫은 위치한 곳에 따라 표시된 경로를 사용합니다:
#[path = "foo.rs"]
mod c;
| 소스 파일 | c 의 파일 위치 | c 의 모듈 경로 |
|---|---|---|
src/a/b.rs | src/a/foo.rs | crate::a::b::c |
src/a/mod.rs | src/a/foo.rs | crate::a::c |
인라인 모듈 블록 내부의 path 속성의 경우, 파일 경로의 상대적 위치는 path 속성이 위치한 소스 파일의 종류에 따라 달라집니다. “mod-rs” 소스 파일은 루트 모듈(lib.rs 또는 main.rs 등) 및 mod.rs 라는 이름의 파일을 가진 모듈입니다. “non-mod-rs” 소스 파일은 그 외의 모든 모듈 파일입니다. mod-rs 파일 내의 인라인 모듈 블록 내부의 path 속성에 대한 경로는 인라인 모듈 구성 요소를 디렉토리로 포함하여 mod-rs 파일의 디렉토리에 상대적입니다. non-mod-rs 파일의 경우, 경로가 non-mod-rs 모듈의 이름을 가진 디렉토리로 시작한다는 점을 제외하고는 동일합니다. 예를 들어, 다음 코드 스니펫은 위치한 곳에 따라 표시된 경로를 사용합니다:
mod inline {
#[path = "other.rs"]
mod inner;
}
| 소스 파일 | inner 의 파일 위치 | inner 의 모듈 경로 |
|---|---|---|
src/a/b.rs | src/a/b/inline/other.rs | crate::a::b::inline::inner |
src/a/mod.rs | src/a/inline/other.rs | crate::a::inline::inner |
인라인 모듈의 path 속성 규칙과 그 내부의 중첩된 모듈 규칙을 결합한 예(mod-rs 파일과 non-mod-rs 파일 모두에 적용됨):
#[path = "thread_files"]
mod thread {
// 이 소스 파일의 디렉토리를 기준으로 `thread_files/tls.rs` 에서 `local_data` 모듈을 로드합니다.
#[path = "tls.rs"]
mod local_data;
}
Attributes on modules
모듈은 다른 모든 아이템과 마찬가지로 외부 속성을 허용합니다. 또한 내부 속성도 허용하는데, 본문이 있는 모듈의 경우 { 뒤에, 또는 소스 파일의 시작 부분(선택적인 BOM과 셰뱅 뒤)에 위치합니다.
모듈에서 의미를 갖는 내장 속성은 cfg, deprecated, doc, 린트 검사 속성, path, 그리고 no_implicit_prelude입니다. 모듈은 매크로 속성도 허용합니다.
외부 크레이트 선언
Syntax
ExternCrate → extern crate CrateRef AsClause? ;
CrateRef → IDENTIFIER | self
AsClause → as ( IDENTIFIER | _ )
extern crate 선언 은 외부 크레이트에 대한 의존성을 명시합니다.
그러면 외부 크레이트는 선언 범위 내의 타입 네임스페이스 에서 주어진 식별자 로 바인딩됩니다.
또한, extern crate 가 크레이트 루트에 나타나면, 크레이트 이름이 외부 프렐류드에도 추가되어 모든 모듈의 스코프에 자동으로 포함됩니다.
as 절은 임포트된 크레이트를 다른 이름으로 바인딩하는 데 사용할 수 있습니다.
외부 크레이트는 컴파일 타임에 특정 soname 으로 해석되며, 해당 soname 에 대한 런타임 연결 요구 사항은 런타임 로딩을 위해 링커로 전달됩니다. soname 은 컴파일러의 라이브러리 경로를 스캔하고, 제공된 선택적 crate_name 과 외부 크레이트가 컴파일될 때 선언된 crate_name 속성 을 매칭하여 컴파일 타임에 해석됩니다. crate_name 이 제공되지 않으면, extern crate 선언에 주어진 식별자 와 동일한 기본 name 속성이 가정됩니다.
self 크레이트를 임포트하여 현재 크레이트에 대한 바인딩을 생성할 수 있습니다. 이 경우 바인딩할 이름을 지정하기 위해 as 절을 사용해야 합니다.
extern crate 선언의 세 가지 예시:
extern crate pcre;
extern crate std; // extern crate std as std;와 동일합니다.
extern crate std as ruststd; // 다른 이름으로 'std'를 링크합니다.
러스트 크레이트의 이름을 지을 때 하이픈은 허용되지 않습니다. 그러나 Cargo 패키지는 이를 사용할 수 있습니다. 이러한 경우 Cargo.toml 에서 크레이트 이름을 지정하지 않으면, Cargo는 투명하게 - 를 _ 로 대체합니다. (자세한 내용은 RFC 940을 참조하십시오.)
예시는 다음과 같습니다:
// hello-world Cargo 패키지 임포트
extern crate hello_world; // 하이픈이 밑줄로 대체됨
Underscore imports
extern crate foo as _ 와 같은 형태로 밑줄을 사용하여 이름을 스코프에 바인딩하지 않고 외부 크레이트 의존성을 선언할 수 있습니다. 이는 링크만 필요하고 참조되지는 않는 크레이트에 유용할 수 있으며, 사용되지 않음으로 보고되는 것을 방지합니다.
macro_use 속성 은 평소와 같이 작동하며 매크로 이름을 macro_use 프렐류드로 가져옵니다.
no_link 속성
The no_link attribute may be applied to an extern crate item to prevent linking the crate.
Note
This is helpful, e.g., when only the macros of a crate are needed.
Example
#[no_link] extern crate other_crate; other_crate::some_macro!();
The no_link attribute uses the MetaWord syntax.
The no_link attribute may only be applied to an extern crate declaration.
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
Only the first use of no_link on an extern crate declaration has effect.
Note
rustclints against any use following the first. This may become an error in the future.
Use 선언
Syntax
UseDeclaration → use UseTree ;
UseTree →
( SimplePath? :: )? *
| ( SimplePath? :: )? { ( UseTree ( , UseTree )* ,? )? }
| SimplePath ( as ( IDENTIFIER | _ ) )?
use 선언 은 다른 경로 와 동의어인 하나 이상의 로컬 이름 바인딩을 생성합니다. 일반적으로 use 선언은 모듈 아이템을 참조하는 데 필요한 경로를 단축하기 위해 사용됩니다. 이러한 선언은 모듈 과 블록 에 나타날 수 있으며, 보통 최상단에 위치합니다. use 선언은 때때로 임포트(import) 라고도 불리며, 공개된 경우에는 재내보내기(re-export) 라고도 합니다.
use 선언은 여러 편리한 단축 표기법을 지원합니다:
use a::b::{c, d, e::f, g::h::i};와 같이 중괄호 구문을 사용하여 공통 접두사를 가진 경로 목록을 동시에 바인딩합니다.
use a::b::{self, c, d::e};와 같이self키워드를 사용하여 공통 접두사를 가진 경로 목록과 그들의 공통 부모 모듈을 동시에 바인딩합니다.
use p::q::r as x;구문을 사용하여 대상 이름을 새로운 로컬 이름으로 다시 바인딩합니다. 이는 앞의 두 기능과 함께 사용될 수도 있습니다:use a::b::{self as ab, c as abc}.
- 별표 와일드카드 구문인
use a::b::*;를 사용하여 주어진 접두사와 일치하는 모든 경로를 바인딩합니다.
use a::b::{self as ab, c, d::{*, e::f}};와 같이 이전 기능들의 그룹을 여러 번 중첩하여 사용합니다.
use 선언의 예:
use std::collections::hash_map::{self, HashMap};
fn foo<T>(_: T){}
fn bar(map1: HashMap<String, usize>, map2: hash_map::HashMap<String, usize>){}
fn main() {
// use 선언은 함수 내부에도 존재할 수 있습니다
use std::option::Option::{Some, None};
// 'foo(vec![std::option::Option::Some(1.0f64),
// std::option::Option::None]);'와 동일합니다.
foo(vec![Some(1.0f64), None]);
// `hash_map` 과 `HashMap` 모두 스코프에 있습니다.
let map1 = HashMap::new();
let map2 = hash_map::HashMap::new();
bar(map1, map2);
}
use 가시성
아이템과 마찬가지로, use 선언은 기본적으로 포함하는 모듈에 비공개(private)입니다. 또한 아이템과 마찬가지로 use 선언은 pub 키워드로 한정되면 공개(public)가 될 수 있습니다. 이러한 use 선언은 이름을 다시 내보내기(re-export) 하는 역할을 합니다. 따라서 공개 use 선언은 일부 공개 이름을 다른 대상 정의로, 심지어 다른 모듈 내부의 비공개 정규 경로를 가진 정의로 리디렉션 할 수 있습니다.
이러한 리디렉션의 시퀀스가 순환을 형성하거나 모호하지 않게 해석될 수 없는 경우, 컴파일 타임 오류를 나타냅니다.
다시 내보내기(re-exporting)의 예:
mod quux {
pub use self::foo::{bar, baz};
pub mod foo {
pub fn bar() {}
pub fn baz() {}
}
}
fn main() {
quux::bar();
quux::baz();
}
이 예제에서 quux 모듈은 foo 에 정의된 두 개의 공개 이름을 다시 내보냅니다.
use 경로
The paths that are allowed in a use item follow the SimplePath grammar and are similar to the paths that may be used in an expression. They may create bindings for:
- 이름을 지을 수 있는 아이템
- 열거형 변형
- 내장 타입
- 속성
- Derive macros
macro_rules
연관 아이템, 제네릭 매개변수, 지역 변수, Self 가 포함된 경로, 또는 도구 속성 은 임포트할 수 없습니다. 더 많은 제약 사항은 아래에 설명되어 있습니다.
use 는 임포트된 엔티티의 모든 네임스페이스 에 대한 바인딩을 생성합니다. 단, self 임포트는 타입 네임스페이스에서만 임포트합니다(아래 설명 참조). 예를 들어, 다음은 두 네임스페이스에서 동일한 이름에 대한 바인딩을 생성하는 것을 보여줍니다:
#![allow(unused)]
fn main() {
mod stuff {
pub struct Foo(pub i32);
}
// `Foo` 타입과 `Foo` 생성자를 임포트합니다.
use stuff::Foo;
fn example() {
let ctor = Foo; // 값 네임스페이스에서 `Foo` 를 사용합니다.
let x: Foo = ctor(123); // 타입 네임스페이스에서 `Foo` 를 사용합니다.
}
}
2018 Edition differences
In the 2015 edition,
usepaths are relative to the crate root. For example:mod foo { pub mod example { pub mod iter {} } pub mod baz { pub fn foobaz() {} } } mod bar { // 크레이트 루트에서 `foo` 를 확인합니다. use foo::example::iter; // `::` 접두사는 명시적으로 `foo` 를 확인합니다. // 크레이트 루트로부터. use ::foo::baz::foobaz; } fn main() {}2015 에디션은 use 선언이 extern 프렐류드 를 참조하는 것을 허용하지 않습니다. 따라서 2015 에디션에서 use 선언으로 외부 크레이트를 참조하려면 여전히
extern crate선언이 필요합니다. 2018 에디션부터는use선언에서extern crate와 동일한 방식으로 외부 크레이트 의존성을 지정할 수 있습니다.
as 를 이용한 이름 변경
as 키워드는 임포트된 엔티티의 이름을 변경하는 데 사용될 수 있습니다. 예를 들어:
#![allow(unused)]
fn main() {
// 함수 `foo` 에 대한 비공개 별칭 `bar` 를 생성합니다.
use inner::foo as bar;
mod inner {
pub fn foo() {}
}
}
중괄호 구문
중괄호는 경로의 마지막 세그먼트에서 이전 세그먼트로부터 여러 엔티티를 임포트하거나, 이전 세그먼트가 없는 경우 현재 스코프에서 임포트하는 데 사용될 수 있습니다. 중괄호는 중첩될 수 있으며, 각 세그먼트 그룹이 부모와 논리적으로 결합되어 전체 경로를 형성하는 경로 트리를 생성합니다.
#![allow(unused)]
fn main() {
// 다음에 대한 바인딩을 생성합니다:
// - `std::collections::BTreeSet`
// - `std::collections::hash_map`
// - `std::collections::hash_map::HashMap`
// 다음에 대한 바인딩을 생성합니다:
// - `std::collections::BTreeSet`
// - `std::collections::hash_map`
// - `std::collections::hash_map::HashMap`
use std::collections::{BTreeSet, hash_map::{self, HashMap}};
}
빈 중괄호는 아무것도 임포트하지 않지만, 선행 경로가 접근 가능한지는 확인됩니다.
2018 Edition differences
In the 2015 edition, paths are relative to the crate root, so an import such as
use {foo, bar};will import the namesfooandbarfrom the crate root, whereas starting in 2018, those names are relative to the current scope.
self 임포트
self 키워드는 중괄호 구문 내에서 부모 엔티티를 자신의 이름으로 바인딩하는 데 사용될 수 있습니다.
mod stuff {
pub fn foo() {}
pub fn bar() {}
}
mod example {
// `stuff` 와 `foo` 에 대한 바인딩을 생성합니다.
use crate::stuff::{self, foo};
pub fn baz() {
foo();
stuff::bar();
}
}
fn main() {}
Note
selfmay also be used as the first segment of a path. The usage ofselfas the first segment and inside ausebrace is logically the same; it means the current module of the parent segment, or the current module if there is no parent segment. Seeselfin the paths chapter for more information on the meaning of a leadingself.
self 는 부모 엔티티의 타입 네임스페이스 에서만 바인딩을 생성합니다. 예를 들어, 다음 예시에서는 foo 모듈만 임포트됩니다.
mod bar {
pub mod foo {}
pub fn foo() {}
}
// 이는 `foo` 모듈만 임포트합니다. `foo` 함수는 값 네임스페이스에 존재하므로 임포트되지 않습니다.
use bar::foo::{self};
fn main() {
foo(); //~ 오류: `foo` 는 모듈입니다.
}
글로브 임포트
* 문자는 use 경로의 마지막 세그먼트로 사용하여 이전 세그먼트의 엔티티에서 임포트 가능한 모든 엔티티를 임포트하는 데 사용할 수 있습니다. 예를 들어:
#![allow(unused)]
fn main() {
// `bar` 에 대한 비공개 별칭을 생성합니다.
use foo::*;
mod foo {
fn i_am_private() {}
enum Example {
V1,
V2,
}
pub fn bar() {
// `Example` 열거형의 `V1` 과 `V2` 에 대한
// 지역 별칭을 생성합니다.
use Example::*;
let x = V1;
}
}
}
아이템과 명명된 임포트는 같은 네임스페이스 에서 글로브 임포트로 가져온 이름을 가리는(shadow) 것이 허용됩니다. 즉, 같은 네임스페이스에 다른 아이템에 의해 이미 정의된 이름이 있다면, 글로브 임포트는 가려집니다. 예를 들어:
#![allow(unused)]
fn main() {
// 이것은 `clashing::Foo` 튜플 구조체 생성자에 대한 바인딩을 생성하지만,
// 여기에 정의된 `Foo` 구조체와 충돌하기 때문에 그 타입은 임포트하지
// 않습니다.
//
// 여기서 정의 순서는 중요하지 않음에 유의하세요.
use clashing::*;
struct Foo {
field: f32,
}
fn do_stuff() {
// `clashing::Foo` 의 생성자를 사용합니다.
let f1 = Foo(123);
// 구조체 표현식은 위에서 정의된
// `Foo` 구조체의 타입을 사용합니다.
let f2 = Foo { field: 1.0 };
// 글로브 임포트로 인해 `Bar` 도 스코프에 있습니다.
let z = Bar {};
}
mod clashing {
pub struct Foo(pub i32);
pub struct Bar {}
}
}
Note
For areas where shadowing is not allowed, see name resolution ambiguities.
* 는 첫 번째 또는 중간 세그먼트로 사용할 수 없습니다.
* 는 모듈의 내용을 자기 자신에게 임포트하는 데 사용할 수 없습니다(예: use self::*;).
2018 Edition differences
In the 2015 edition, paths are relative to the crate root, so an import such as
use *;is valid, and it means to import everything from the crate root. This cannot be used in the crate root itself.
Underscore imports
아이템은 use path as _ 형식을 사용하여 이름에 바인딩하지 않고 임포트할 수 있습니다. 이는 트레잇의 심볼을 임포트하지 않고 메서드를 사용하려 할 때(예: 트레잇 심볼이 다른 심볼과 충돌할 수 있는 경우) 특히 유용합니다. 다른 예로는 이름을 임포트하지 않고 외부 크레이트를 링크하는 경우가 있습니다.
별표 글로브 임포트는 _ 로 임포트된 아이템을 이름 지을 수 없는 형태로 임포트합니다.
mod foo {
pub trait Zoo {
fn zoo(&self) {}
}
impl<T> Zoo for T {}
}
use self::foo::Zoo as _;
struct Zoo; // 밑줄 임포트는 이 아이템과의 이름 충돌을 방지합니다.
fn main() {
let z = Zoo;
z.zoo();
}
고유하고 이름 지을 수 없는 심볼은 매크로 확장 후에 생성되므로, 매크로는 안전하게 _ 임포트에 대한 다중 참조를 내보낼 수 있습니다. 예를 들어, 다음 코드는 오류를 생성하지 않아야 합니다:
#![allow(unused)]
fn main() {
macro_rules! m {
($item: item) => { $item $item }
}
m!(use std as _;);
// 다음과 같이 확장됩니다:
// use std as _;
// use std as _;
}
제약 사항
The following rules are restrictions for valid use declarations.
When using crate to import the current crate, you must use as to define the binding name.
Example
#![allow(unused)] fn main() { use crate as root; use crate::{self as root2}; // Not allowed: // use crate; // use crate::{self}; }
When using $crate in a macro transcriber to import the current crate, you must use as to define the binding name.
Example
#![allow(unused)] fn main() { macro_rules! import_crate_root { () => { use $crate as my_crate; use $crate::{self as my_crate2}; }; } }
When using self to import the current module, you must use as to define the binding name.
Example
#![allow(unused)] fn main() { use {self as this_module}; use self as this_module2; use self::{self as this_module3}; // Not allowed: // use {self}; // use self; // use self::{self}; }
When using super to import a parent module, you must use as to define the binding name.
Example
#![allow(unused)] fn main() { mod a { mod b { use super as parent; use super::{self as parent2}; use self::super as parent3; use super::super as grandparent; use super::super::{self as grandparent2}; // Not allowed: // use super; // use super::{self}; // use self::super; // use super::super; // use super::super::{self}; } } }
:: as the extern prelude cannot be imported.
Example
#![allow(unused)] fn main() { use ::{self as root}; //~ Error }
2018 Edition differences
In the 2015 edition, the prefix
::refers to the crate root, souse ::{self as root};is allowed because it is same asuse crate::{self as root};. Starting with the 2018 edition the::prefix refers to the extern prelude, which cannot be directly imported.#![allow(unused)] fn main() { use ::{self as root}; //~ Ok }
다른 아이템 정의와 마찬가지로, use 임포트는 모듈이나 블록 내의 동일한 네임스페이스에서 같은 이름으로 중복 바인딩을 생성할 수 없습니다.
use paths cannot refer to enum variants through a type alias.
Example
#![allow(unused)] fn main() { enum MyEnum { MyVariant } type TypeAlias = MyEnum; use MyEnum::MyVariant; //~ OK use TypeAlias::MyVariant; //~ 오류 }
함수
Syntax
Function →
FunctionQualifiers fn IDENTIFIER GenericParams?
( FunctionParameters? )
FunctionReturnType? WhereClause?
( BlockExpression | ; )
FunctionQualifiers → const? async?1 ItemSafety?2 ( extern Abi? )?
ItemSafety → safe3 | unsafe
Abi → STRING_LITERAL | RAW_STRING_LITERAL
FunctionParameters →
SelfParam ,?
| ( SelfParam , )? FunctionParam ( , FunctionParam )* ,?
SelfParam → OuterAttribute* ( ShorthandSelf | TypedSelf )
ShorthandSelf → ( & | & Lifetime )? mut? self
FunctionParam → OuterAttribute* ( FunctionParamPattern | … | Type4 )
FunctionParamPattern → PatternNoTopAlt : ( Type | … )
FunctionReturnType → -> Type
함수 는 블록(함수의 본문)과 이름, 매개변수 집합, 출력 타입으로 구성됩니다. 이름을 제외한 나머지는 모두 선택 사항입니다.
함수는 fn 키워드로 선언되며, 이는 해당 함수가 위치한 모듈이나 블록의 값 네임스페이스 에 주어진 이름을 정의합니다.
함수는 호출자가 함수에 인수를 전달하는 입력 변수 집합을 매개변수로 선언할 수 있으며, 함수가 완료될 때 호출자에게 반환할 값의 출력 타입 을 선언할 수 있습니다.
출력 타입이 명시적으로 지정되지 않은 경우, 유닛 타입 입니다.
참조될 때, _함수 _는 해당 0 크기 _ 함수 아이템 타입_ 의 일급(first-class) 값 을 산출합니다, 호출될 때 함수에 대한 직접 호출로 평가됩니다.
예를 들어, 이것은 간단한 함수입니다:
#![allow(unused)]
fn main() {
fn answer_to_life_the_universe_and_everything() -> i32 {
return 42;
}
}
safe 함수는 시맨틱적으로 extern 블록 에서 사용될 때만 허용됩니다.
함수 매개변수
함수 매개변수는 반박 불가능한(irrefutable) 패턴 이므로, else 없는 let 바인딩에서 유효한 모든 패턴은 매개변수로도 유효합니다:
#![allow(unused)]
fn main() {
fn first((value, _): (i32, i32)) -> i32 { value }
}
If the first parameter is a SelfParam, this indicates that the function is a method.
self 매개변수가 있는 함수는 트레잇 이나 구현 의 연관 함수 로만 나타날 수 있습니다.
... 토큰이 있는 매개변수는 가변 인자 함수 임을 나타내며, 오직 외부 블록 함수의 마지막 매개변수로만 사용될 수 있습니다. 가변 인자 매개변수는 args: ... 와 같이 선택적인 식별자를 가질 수 있습니다.
함수 본문
함수의 본문 블록은 개념적으로 인자 패턴을 먼저 바인딩한 다음 함수 본문의 값을 return 하는 또 다른 블록으로 감싸져 있습니다. 이는 블록의 꼬리 표현식이 평가될 경우 결국 호출자에게 반환됨을 의미합니다. 평소와 같이, 함수 본문 내의 명시적인 return 표현식은 도달할 경우 해당 암시적 반환을 생략합니다.
예를 들어, 위의 함수는 마치 다음과 같이 작성된 것처럼 동작합니다:
// argument_0은 호출자로부터 전달된 실제 첫 번째 인자입니다.
let (value, _) = argument_0;
return {
value
};
본문 블록이 없는 함수는 세미콜론으로 종료됩니다. 이러한 형식은 트레잇 이나 외부 블록 에서만 나타날 수 있습니다.
제네릭 함수
제네릭 함수 는 하나 이상의 매개변수화된 타입 이 해당 시그니처에 나타날 수 있도록 합니다. 각 타입 매개변수는 함수 이름 뒤에 오는 꺾쇠괄호로 둘러싸이고 쉼표로 구분된 목록에 명시적으로 선언되어야 합니다.
#![allow(unused)]
fn main() {
// foo는 A와 B에 대해 제네릭합니다.
fn foo<A, B>(x: A, y: B) {
}
}
함수 시그니처와 본문 내부에서, 타입 매개변수의 이름은 타입 이름으로 사용될 수 있습니다.
타입 매개변수에 대해 트레잇 바운드를 지정하여 해당 트레잇의 메서드를 해당 타입의 값에 대해 호출할 수 있도록 할 수 있습니다. 이는 where 구문을 사용하여 지정됩니다:
#![allow(unused)]
fn main() {
use std::fmt::Debug;
fn foo<T>(x: T) where T: Debug {
}
}
제네릭 함수가 참조될 때, 그 타입은 참조의 컨텍스트에 따라 인스턴스화됩니다. 예를 들어, 여기서 foo 함수를 호출하면:
#![allow(unused)]
fn main() {
use std::fmt::Debug;
fn foo<T>(x: &[T]) where T: Debug {
// 세부 사항 생략
}
foo(&[1, 2]);
}
타입 매개변수 T 를 i32 로 인스턴스화할 것입니다.
타입 매개변수는 함수 이름 뒤의 후행 경로 구성 요소에 명시적으로 제공될 수도 있습니다. 이는 타입 매개변수를 결정하기 위한 컨텍스트가 충분하지 않은 경우 필요할 수 있습니다. 예를 들어, mem::size_of::<u32>() == 4 와 같습니다.
외부 함수 한정자
extern 함수 한정자는 특정 ABI로 호출될 수 있는 함수 정의 를 제공할 수 있게 합니다:
extern "ABI" fn foo() { /* ... */ }
이들은 종종 함수의 정의 를 제공하지 않고도 함수를 호출할 수 있게 해주는 함수 선언 을 제공하는 외부 블록 아이템과 결합되어 사용됩니다.
unsafe extern "ABI" {
unsafe fn foo(); /* 본문 없음 */
safe fn bar(); /* 본문 없음 */
}
unsafe { foo() };
bar();
함수 아이템의 FunctionQualifiers 에서 "extern" Abi?* 가 생략되면, "Rust" ABI가 할당됩니다. 예를 들어:
#![allow(unused)]
fn main() {
fn foo() {}
}
다음과 동일합니다:
#![allow(unused)]
fn main() {
extern "Rust" fn foo() {}
}
함수는 외부 코드에 의해 호출될 수 있으며, Rust와 다른 ABI를 사용하면 C와 같은 다른 프로그래밍 언어에서 호출할 수 있는 함수를 제공할 수 있습니다:
#![allow(unused)]
fn main() {
// "C" ABI를 가진 함수를 선언합니다.
extern "C" fn new_i32() -> i32 { 0 }
// "stdcall" ABI를 가진 함수를 선언합니다.
#[cfg(any(windows, target_arch = "x86"))]
extern "stdcall" fn new_i32_stdcall() -> i32 { 0 }
}
외부 블록 과 마찬가지로, extern 키워드가 사용되고 "ABI" 가 생략되면, 사용되는 ABI는 "C" 가 기본값입니다. 즉, 다음은:
#![allow(unused)]
fn main() {
extern fn new_i32() -> i32 { 0 }
let fptr: extern fn() -> i32 = new_i32;
}
다음과 동일합니다:
#![allow(unused)]
fn main() {
extern "C" fn new_i32() -> i32 { 0 }
let fptr: extern "C" fn() -> i32 = new_i32;
}
Unwinding
Most ABI strings come in two variants, one with an -unwind suffix and one without. The Rust ABI always permits unwinding, so there is no Rust-unwind ABI. The choice of ABI, together with the runtime panic handler, determines the behavior when unwinding out of a function.
The table below indicates the behavior of an unwinding operation reaching each type of ABI boundary (function declaration or definition using the corresponding ABI string). Note that the Rust runtime is not affected by, and cannot have an effect on, any unwinding that occurs entirely within another language’s runtime, that is, unwinds that are thrown and caught without reaching a Rust ABI boundary.
The panic-unwind column refers to panicking via the panic! macro and similar standard library mechanisms, as well as to any other Rust operations that cause a panic, such as out-of-bounds array indexing or integer overflow.
The “unwinding” ABI category refers to "Rust" (the implicit ABI of Rust functions not marked extern), "C-unwind", and any other ABI with -unwind in its name. The “non-unwinding” ABI category refers to all other ABI strings, including "C" and "stdcall".
Native unwinding is defined per-target. On targets that support throwing and catching C++ exceptions, it refers to the mechanism used to implement this feature. Some platforms implement a form of unwinding referred to as “forced unwinding”; longjmp on Windows and pthread_exit in glibc are implemented this way. Forced unwinding is explicitly excluded from the “Native unwind” column in the table.
| panic runtime | ABI | panic-unwind | Native unwind (unforced) |
|---|---|---|---|
panic=unwind | unwinding | unwind | unwind |
panic=unwind | non-unwinding | abort (see notes below) | undefined behavior |
panic=abort | unwinding | panic aborts without unwinding | abort |
panic=abort | non-unwinding | panic aborts without unwinding | undefined behavior |
With panic=unwind, when a panic is turned into an abort by a non-unwinding ABI boundary, either no destructors (Drop calls) will run, or all destructors up until the ABI boundary will run. It is unspecified which of those two behaviors will happen.
For other considerations and limitations regarding unwinding across FFI boundaries, see the relevant section in the Panic documentation.
const 함수
See const functions for the definition of const functions.
비동기 함수
함수는 async 로 한정될 수 있으며, 이는 unsafe 한정자와 결합될 수도 있습니다:
#![allow(unused)]
fn main() {
async fn regular_example() { }
async unsafe fn unsafe_example() { }
}
비동기 함수는 호출될 때 작업을 수행하지 않습니다. 대신 인수를 퓨처(future)로 캡처합니다. 폴링(polled)될 때, 해당 퓨처는 함수의 본문을 실행합니다.
비동기 함수(async function)는 impl Future 를 반환하고 async move 블록 을 본문으로 갖는 함수와 거의 동일합니다:
#![allow(unused)]
fn main() {
// 소스
async fn example(x: &str) -> usize {
x.len()
}
}
다음과 거의 동일합니다:
#![allow(unused)]
fn main() {
use std::future::Future;
// 디슈거링(Desugared)
fn example<'a>(x: &'a str) -> impl Future<Output = usize> + 'a {
async move { x.len() }
}
}
실제 디슈거링은 더 복잡합니다:
- 디슈거링에서의 반환 타입은
async fn선언의 모든 라이프타임 매개변수를 캡처한다고 가정합니다. 이는 위의 디슈거링 예시에서'a보다 명시적으로 오래 살며, 따라서 이를 캡처하는 것에서 확인할 수 있습니다.
- 본문의
async move블록 은 사용되지 않거나_패턴에 바인딩된 매개변수를 포함하여 모든 함수 매개변수를 캡처합니다. 이는 함수가 비동기가 아닐 때와 동일한 순서로 함수 매개변수가 드롭되도록 보장하지만, 드롭은 반환된 퓨처가 완전히 어웨이트(awaited)되었을 때 발생한다는 점이 다릅니다.
비동기의 효과에 대한 자세한 내용은 async 블록 을 참조하십시오.
2018 Edition differences
Async functions are only available beginning with Rust 2018.
async 와 unsafe 결합하기
비동기이면서 동시에 안전하지 않은(unsafe) 함수를 선언하는 것은 합법입니다. 결과 함수는 호출하기에 안전하지 않으며 (다른 비동기 함수와 마찬가지로) 퓨처를 반환합니다. 이 퓨처는 일반적인 퓨처이므로 이를 “어웨이트(await)” 하는 데 unsafe 컨텍스트가 필요하지 않습니다.
#![allow(unused)]
fn main() {
// 어웨이트되었을 때 `x` 를 역참조하는 퓨처를 반환합니다.
//
// 건전성 조건: 결과 퓨처가 완료될 때까지 `x` 를 안전하게 역참조할 수 있어야 합니다.
async unsafe fn unsafe_example(x: *const i32) -> i32 {
*x
}
async fn safe_example() {
// 함수를 처음 호출하려면 `unsafe` 블록이 필요합니다.
let p = 22;
let future = unsafe { unsafe_example(&p) };
// 하지만 여기서는 `unsafe` 블록이 필요하지 않습니다. 이는 `p` 의 값을 읽을 것입니다.
let q = future.await;
}
}
이 동작은 impl Future 를 반환하는 함수로의 디슈거링 결과입니다. 이 경우 디슈거링된 함수는 unsafe 함수이지만, 반환 값은 동일하게 유지됩니다.
Unsafe는 다른 함수에서 사용되는 것과 정확히 동일한 방식으로 비동기 함수에서 사용됩니다. 이는 함수가 건전성을 보장하기 위해 호출자에게 몇 가지 추가적인 의무를 부과함을 나타냅니다. 다른 모든 unsafe 함수와 마찬가지로, 이러한 조건은 초기 호출 자체를 넘어 확장될 수 있습니다. 예를 들어, 위의 스니펫에서 unsafe_example 함수는 포인터 x 를 인자로 받았고, (어웨이트되었을 때) 해당 포인터를 역참조했습니다. 이는 퓨처가 실행을 마칠 때까지 x 가 유효해야 함을 의미하며, 이를 보장하는 것은 호출자의 책임입니다.
함수의 속성
외부 속성 은 함수에 허용됩니다. 내부 속성 은 함수 본문 블록 내부의 { 바로 뒤에 허용됩니다.
이 예시는 함수의 내부 속성을 보여줍니다. 이 함수는 단지 “Example“이라는 단어로 문서화됩니다.
#![allow(unused)]
fn main() {
fn documented() {
#![doc = "예시"]
}
}
Note
Except for lints, it is idiomatic to only use outer attributes on function items.
The attributes that have meaning on a function are:
cfg_attrcfgcolddeprecateddocexport_nameinlinelink_sectionmust_useno_mangle- Lint check attributes
- Procedural macro attributes
- Testing attributes
함수 매개변수의 속성
외부 속성 은 함수 매개변수에 허용되며, 허용되는 내장 속성은 cfg, cfg_attr, allow, warn, deny, forbid 로 제한됩니다.
#![allow(unused)]
fn main() {
fn len(
#[cfg(windows)] slice: &[u16],
#[cfg(not(windows))] slice: &[u8],
) -> usize {
slice.len()
}
}
아이템에 적용되는 절차적 매크로 속성에서 사용하는 비활성(inert) 도우미 속성도 허용되지만, 최종 TokenStream 에 이러한 비활성 속성을 포함하지 않도록 주의해야 합니다.
예를 들어, 다음 코드는 어디에도 공식적으로 정의되지 않은 비활성 some_inert_attribute 속성을 정의하고, some_proc_macro_attribute 절차적 매크로가 그 존재를 감지하여 출력 토큰 스트림에서 제거하는 역할을 담당합니다.
#[some_proc_macro_attribute]
fn foo_oof(#[some_inert_attribute] arg: u8) {
}
-
async한정자는 2015 에디션에서 허용되지 않습니다. ↩ -
Rust 2024 이전 에디션 관련:
extern블록 내에서,safe또는unsafe함수 한정자는extern이unsafe로 한정될 때만 허용됩니다. ↩ -
safe함수 한정자는 시맨틱적으로extern블록 내에서만 허용됩니다. ↩
타입 별칭
Syntax
TypeAlias →
type IDENTIFIER GenericParams? ( : TypeParamBounds )?
WhereClause?
( = Type WhereClause? )? ;
타입 별칭 은 해당 모듈이나 블록의 타입 네임스페이스 에 있는 기존 타입 에 대한 새로운 이름을 정의합니다. 타입 별칭은 type 키워드로 선언됩니다. 모든 값은 단일하고 구체적인 타입을 갖지만, 여러 다른 트레잇을 구현할 수 있으며, 여러 다른 타입 제약과 호환될 수 있습니다.
예를 들어, 다음은 Point 타입을 (u8, u8)(부호 없는 8비트 정수 쌍의 타입)의 동의어로 정의합니다:
#![allow(unused)]
fn main() {
type Point = (u8, u8);
let p: Point = (41, 68);
}
튜플 구조체나 유닛 구조체에 대한 타입 별칭은 해당 타입의 생성자를 한정하는 데 사용할 수 없습니다:
#![allow(unused)]
fn main() {
struct MyStruct(u32);
use MyStruct as UseAlias;
type TypeAlias = MyStruct;
let _ = UseAlias(5); // OK
let _ = TypeAlias(5); // 작동하지 않음
}
A type alias, when not used as an associated type, must include a Type and may not include TypeParamBounds.
A type alias, when used as an associated type in a trait, must not include a Type specification but may include TypeParamBounds.
A type alias, when used as an associated type in a trait impl, must include a Type specification and may not include TypeParamBounds.
Where clauses before the equals sign on a type alias in a trait impl (like type TypeAlias<T> where T: Foo = Bar<T>) are deprecated. Where clauses after the equals sign (like type TypeAlias<T> = Bar<T> where T: Foo) are preferred.
구조체
Syntax
Struct →
StructStruct
| TupleStruct
StructStruct →
struct IDENTIFIER GenericParams? WhereClause? ( { StructFields? } | ; )
TupleStruct →
struct IDENTIFIER GenericParams? ( TupleFields? ) WhereClause? ;
StructFields → StructField ( , StructField )* ,?
StructField → OuterAttribute* Visibility? IDENTIFIER : Type
TupleFields → TupleField ( , TupleField )* ,?
구조체(struct) 는 struct 키워드로 정의된 명목상의 구조체 타입 입니다.
구조체 선언은 해당 모듈이나 블록의 타입 네임스페이스 에 주어진 이름을 정의합니다.
struct 아이템과 그 사용 예시:
#![allow(unused)]
fn main() {
struct Point {x: i32, y: i32}
let p = Point {x: 10, y: 11};
let px: i32 = p.x;
}
튜플 구조체 는 명목상의 튜플 타입 이며, 역시 struct 키워드로 정의됩니다. 타입을 정의하는 것 외에도, 값 네임스페이스 에 동일한 이름의 생성자를 정의합니다. 생성자는 구조체의 새 인스턴스를 생성하기 위해 호출할 수 있는 함수입니다. 예를 들어:
#![allow(unused)]
fn main() {
struct Point(i32, i32);
let p = Point(10, 11);
let px: i32 = match p { Point(x, _) => x };
}
유닛 유사 구조체(unit-like struct) 는 필드 목록을 완전히 생략하여 정의된, 필드가 없는 구조체입니다. 이러한 구조체는 암시적으로 동일한 이름의 해당 타입 상수 를 정의합니다. 예를 들어:
#![allow(unused)]
fn main() {
struct Cookie;
let c = [Cookie, Cookie {}, Cookie, Cookie {}];
}
다음과 동일합니다.
#![allow(unused)]
fn main() {
struct Cookie {}
const Cookie: Cookie = Cookie {};
let c = [Cookie, Cookie {}, Cookie, Cookie {}];
}
구조체의 정확한 메모리 레이아웃은 지정되어 있지 않습니다. repr 속성 을 사용하여 특정 레이아웃을 지정할 수 있습니다.
열거형
Syntax
Enumeration →
enum IDENTIFIER GenericParams? WhereClause? { EnumVariants? }
EnumVariants → EnumVariant ( , EnumVariant )* ,?
EnumVariant →
OuterAttribute* Visibility?
IDENTIFIER ( EnumVariantTuple | EnumVariantStruct )? EnumVariantDiscriminant?
EnumVariantTuple → ( TupleFields? )
EnumVariantStruct → { StructFields? }
열거형(enumeration) 또는 줄여서 enum 은 명목상의 열거 타입 과 함께, 해당 열거 타입의 값을 생성하거나 패턴 매칭하는 데 사용할 수 있는 생성자 세트를 동시에 정의하는 것입니다.
열거형은 enum 키워드로 선언됩니다.
enum 선언은 해당 모듈이나 블록의 타입 네임스페이스 에 열거형 타입을 정의합니다.
enum 아이템과 그 사용 예시:
#![allow(unused)]
fn main() {
enum Animal {
Dog,
Cat,
}
let mut a: Animal = Animal::Dog;
a = Animal::Cat;
}
열거형 생성자는 이름을 가진 필드나 이름이 없는 필드를 가질 수 있습니다:
#![allow(unused)]
fn main() {
enum Animal {
Dog(String, f64),
Cat { name: String, weight: f64 },
}
let mut a: Animal = Animal::Dog("Cocoa".to_string(), 37.2);
a = Animal::Cat { name: "Spotty".to_string(), weight: 2.7 };
}
이 예제에서 Cat 은 구조체형 열거형 변형(struct-like enum variant) 인 반면, Dog 는 단순히 열거형 변형이라고 부릅니다.
An enum where no constructors contain fields is called a field-less enum. For example, this is a fieldless enum:
#![allow(unused)]
fn main() {
enum Fieldless {
Tuple(),
Struct{},
Unit,
}
}
필드 없는 열거형이 유닛 변형만 포함하는 경우, 해당 열거형을 _유닛 전용 열거형(unit-only enum)_이라고 합니다. 예를 들어:
#![allow(unused)]
fn main() {
enum Enum {
Foo = 3,
Bar = 2,
Baz = 1,
}
}
변형 생성자는 구조체 정의와 유사하며, use 선언 을 포함하여 열거형 이름의 경로로 참조할 수 있습니다.
각 변형은 타입 네임스페이스 에 해당 타입을 정의하지만, 그 타입은 타입 지정자로 사용할 수 없습니다. 튜플형 및 유닛형 변형은 값 네임스페이스에 생성자도 정의합니다.
구조체형 변형은 구조체 표현식 으로 인스턴스화할 수 있습니다.
튜플형 변형은 호출 표현식 이나 구조체 표현식으로 인스턴스화할 수 있습니다.
유닛형 변형은 경로 표현식 이나 구조체 표현식으로 인스턴스화할 수 있습니다. 예를 들어:
#![allow(unused)]
fn main() {
enum Examples {
UnitLike,
TupleLike(i32),
StructLike { value: i32 },
}
use Examples::*; // 모든 변형에 대한 별칭을 생성합니다.
let x = UnitLike; // 상수 아이템의 경로 표현식.
let x = UnitLike {}; // 구조체 표현식.
let y = TupleLike(123); // 호출 표현식.
let y = TupleLike { 0: 123 }; // 정수 필드 이름을 사용하는 구조체 표현식.
let z = StructLike { value: 123 }; // 구조체 표현식.
}
판별자
각 열거형 인스턴스는 판별자(discriminant) 를 가집니다. 이는 어떤 변형을 보유하고 있는지 결정하는 데 사용되는 논리적으로 연결된 정수입니다.
Rust 표현 하에서, 판별자는 isize 값으로 해석됩니다. 하지만, 컴파일러는 실제 메모리 레이아웃에서 더 작은 타입(또는 변형을 구별하는 다른 수단)을 사용하는 것이 허용됩니다.
판별자 값 할당
명시적 판별자
두 가지 상황에서, 변형 이름 뒤에 = 와 상수 표현식을 붙여 변형의 판별자를 명시적으로 설정할 수 있습니다:
- 열거형이 “유닛 전용“인 경우.
-
원시 표현(primitive representation)이 사용되는 경우. 예를 들어:
#![allow(unused)] fn main() { #[repr(u8)] enum Enum { Unit = 3, Tuple(u16), Struct { a: u8, b: u16, } = 1, } }
암시적 판별자
변형에 대한 판별자가 지정되지 않으면, 선언 내 이전 변형의 판별자보다 1 큰 값으로 설정됩니다. 선언의 첫 번째 변형의 판별자가 지정되지 않으면, 0으로 설정됩니다.
#![allow(unused)]
fn main() {
enum Foo {
Bar, // 0
Baz = 123, // 123
Quux, // 124
}
let baz_discriminant = Foo::Baz as u32;
assert_eq!(baz_discriminant, 123);
}
제약 사항
두 변형이 동일한 판별자를 공유하면 오류입니다.
#![allow(unused)]
fn main() {
enum SharedDiscriminantError {
SharedA = 1,
SharedB = 1
}
enum SharedDiscriminantError2 {
Zero, // 0
One, // 1
OneToo = 1 // 1 (이전과 충돌!)
}
}
이전 판별자가 판별자 크기의 최댓값일 때 판별자를 지정하지 않는 것도 오류입니다.
#![allow(unused)]
fn main() {
#[repr(u8)]
enum OverflowingDiscriminantError {
Max = 255,
MaxPlusOne // 256이어야 하지만, 열거형을 오버플로합니다.
}
#[repr(u8)]
enum OverflowingDiscriminantError2 {
MaxMinusOne = 254, // 254
Max, // 255
MaxPlusOne // 256이어야 하지만, 열거형을 오버플로합니다.
}
}
판별자 접근
mem::discriminant 를 통해
std::mem::discriminant 는 비교 가능한 열거형 값의 판별자에 대한 불투명한(opaque) 참조를 반환합니다. 이는 판별자의 값을 얻는 데 사용할 수 없습니다.
캐스팅
열거형이 유닛 전용(튜플 및 구조체 변형이 없음)인 경우, 숫자 캐스트 를 사용하여 판별자에 직접 접근할 수 있습니다. 예를 들어:
#![allow(unused)]
fn main() {
enum Enum {
Foo,
Bar,
Baz,
}
assert_eq!(0, Enum::Foo as isize);
assert_eq!(1, Enum::Bar as isize);
assert_eq!(2, Enum::Baz as isize);
}
Field-less enums can be cast if they do not have explicit discriminants, or where only unit variants are explicit.
#![allow(unused)]
fn main() {
enum Fieldless {
Tuple(),
Struct{},
Unit,
}
assert_eq!(0, Fieldless::Tuple() as isize);
assert_eq!(1, Fieldless::Struct{} as isize);
assert_eq!(2, Fieldless::Unit as isize);
#[repr(u8)]
enum FieldlessWithDiscriminants {
First = 10,
Tuple(),
Second = 20,
Struct{},
Unit,
}
assert_eq!(10, FieldlessWithDiscriminants::First as u8);
assert_eq!(11, FieldlessWithDiscriminants::Tuple() as u8);
assert_eq!(20, FieldlessWithDiscriminants::Second as u8);
assert_eq!(21, FieldlessWithDiscriminants::Struct{} as u8);
assert_eq!(22, FieldlessWithDiscriminants::Unit as u8);
}
포인터 캐스팅
열거형이 원시 표현 을 지정하는 경우, unsafe 포인터 캐스팅을 통해 판별자에 안정적으로 접근할 수 있습니다:
#![allow(unused)]
fn main() {
#[repr(u8)]
enum Enum {
Unit,
Tuple(bool),
Struct{a: bool},
}
impl Enum {
fn discriminant(&self) -> u8 {
unsafe { *(self as *const Self as *const u8) }
}
}
let unit_like = Enum::Unit;
let tuple_like = Enum::Tuple(true);
let struct_like = Enum::Struct{a: false};
assert_eq!(0, unit_like.discriminant());
assert_eq!(1, tuple_like.discriminant());
assert_eq!(2, struct_like.discriminant());
}
0-변형 열거형
변형이 0개인 열거형을 0-변형 열거형(zero-variant enums) 이라고 합니다. 유효한 값이 없으므로 인스턴스화할 수 없습니다.
#![allow(unused)]
fn main() {
enum ZeroVariants {}
}
0-변형 열거형은 never 타입 과 동일하지만, 다른 타입으로 강제 변환(coerced)될 수 없습니다.
#![allow(unused)]
fn main() {
enum ZeroVariants {}
let x: ZeroVariants = panic!();
let y: u32 = x; // 타입 불일치 오류
}
변형 가시성
Enum variants syntactically allow a Visibility annotation, but this is rejected when the enum is validated. This allows items to be parsed with a unified syntax across different contexts where they are used.
#![allow(unused)]
fn main() {
macro_rules! mac_variant {
($vis:vis $name:ident) => {
enum $name {
$vis Unit,
$vis Tuple(u8, u16),
$vis Struct { f: u8 },
}
}
}
// 빈 `vis` 는 허용됩니다.
mac_variant! { E }
// 검증되기 전에 제거되므로 허용됩니다.
#[cfg(false)]
enum E {
pub U,
pub(crate) T(u8),
pub(super) T { f: String }
}
}
공용체
Syntax
Union →
union IDENTIFIER GenericParams? WhereClause? { StructFields? }
공용체 선언은 struct 대신 union 을 사용한다는 점을 제외하면 구조체 선언과 동일한 구문을 사용합니다.
공용체 선언은 해당 모듈이나 블록의 타입 네임스페이스 에 주어진 이름을 정의합니다.
#![allow(unused)]
fn main() {
#[repr(C)]
union MyUnion {
f1: u32,
f2: f32,
}
}
The key property of unions is that all fields of a union share common storage. As a result, writes to one field of a union can overwrite its other fields, and size of a union is determined by the size of its largest field.
공용체 필드 타입은 다음 타입의 하위 집합으로 제한됩니다:
Copy타입
- 참조(임의의
T에 대한&T및&mut T)
ManuallyDrop<T>(임의의T에 대해)
- 허용된 공용체 필드 타입만 포함하는 튜플 및 배열
이 제약은 특히 공용체 필드가 절대 드롭(drop)될 필요가 없음을 보장합니다. 구조체나 열거형과 마찬가지로, 공용체에 대해 impl Drop 을 구현하여 드롭될 때 일어날 일을 수동으로 정의할 수 있습니다.
필드가 없는 공용체는 컴파일러에서 허용되지 않지만, 매크로에서는 허용될 수 있습니다.
공용체 초기화
공용체 타입의 값은 구조체 타입에 사용되는 것과 동일한 구문을 사용하여 생성할 수 있지만, 정확히 하나의 필드만 지정해야 합니다:
#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }
let u = MyUnion { f1: 1 };
}
위의 표현식은 MyUnion 타입의 값을 생성하고 f1 필드를 사용하여 저장 공간을 초기화합니다. 공용체는 구조체 필드와 동일한 구문을 사용하여 접근할 수 있습니다:
#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }
let u = MyUnion { f1: 1 };
let f = unsafe { u.f1 };
}
공용체 필드 읽기 및 쓰기
공용체에는 “활성 필드(active field)“라는 개념이 없습니다. 대신, 모든 공용체 접근은 저장 공간을 접근에 사용된 필드의 타입으로 해석할 뿐입니다.
공용체 필드를 읽는 것은 필드의 타입에서 공용체의 비트를 읽는 것입니다.
필드는 0이 아닌 오프셋을 가질 수 있습니다(C 표현 이 사용된 경우 제외). 이 경우 필드의 오프셋에서 시작하는 비트를 읽습니다
데이터가 필드의 타입에서 유효한지 확인하는 것은 프로그래머의 책임입니다. 이를 지키지 않으면 정의되지 않은 동작(undefined behavior) 이 발생합니다. 예를 들어, 불리언 타입 의 필드에서 값 3 을 읽는 것은 정의되지 않은 동작입니다. 사실상, C 표현 을 사용하는 공용체에 쓴 다음 읽는 것은 쓰기에 사용된 타입에서 읽기에 사용된 타입으로의 transmute 와 유사합니다.
결과적으로, 공용체 필드의 모든 읽기는 unsafe 블록 내에 배치되어야 합니다:
#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }
let u = MyUnion { f1: 1 };
unsafe {
let f = u.f1;
}
}
일반적으로 공용체를 사용하는 코드는 안전하지 않은 공용체 필드 접근에 대한 안전한 래퍼를 제공합니다.
반면에 공용체 필드에 쓰는 것은 안전합니다. 임의의 데이터를 덮어쓸 뿐이며 정의되지 않은 동작을 유발할 수 없기 때문입니다. (공용체 필드 타입은 절대 드롭 글루(drop glue)를 가질 수 없으므로, 공용체 필드 쓰기는 절대 암시적으로 아무것도 드롭하지 않음에 유의하십시오.)
공용체 패턴 매칭
공용체 필드에 접근하는 또 다른 방법은 패턴 매칭을 사용하는 것입니다.
공용체 필드에 대한 패턴 매칭은 구조체 패턴과 동일한 구문을 사용하지만, 패턴이 정확히 하나의 필드만 지정해야 한다는 점이 다릅니다.
패턴 매칭은 특정 필드로 공용체를 읽는 것과 같으므로, 마찬가지로 unsafe 블록 내에 배치되어야 합니다.
#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }
fn f(u: MyUnion) {
unsafe {
match u {
MyUnion { f1: 10 } => { println!("ten"); }
MyUnion { f2 } => { println!("{}", f2); }
}
}
}
}
패턴 매칭은 더 큰 구조체의 필드로서 공용체와 매칭할 수 있습니다. 특히, FFI를 통해 C 태그가 지정된 공용체(C tagged union)를 구현하기 위해 Rust 공용체를 사용할 때, 이를 통해 태그와 해당 필드에 동시에 매칭할 수 있습니다:
#![allow(unused)]
fn main() {
#[repr(u32)]
enum Tag { I, F }
#[repr(C)]
union U {
i: i32,
f: f32,
}
#[repr(C)]
struct Value {
tag: Tag,
u: U,
}
fn is_zero(v: Value) -> bool {
unsafe {
match v {
Value { tag: Tag::I, u: U { i: 0 } } => true,
Value { tag: Tag::F, u: U { f: num } } if num == 0.0 => true,
_ => false,
}
}
}
}
공용체 필드에 대한 참조
공용체 필드는 공통 저장 공간을 공유하므로, 공용체의 한 필드에 대한 쓰기 접근 권한을 얻으면 나머지 모든 필드에 대한 쓰기 접근 권한을 얻을 수 있습니다.
대여 검사(borrow checking) 규칙은 이 사실을 고려하여 조정되어야 합니다. 결과적으로 공용체의 한 필드가 대여되면 나머지 모든 필드도 동일한 수명 동안 대여됩니다.
#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }
// 오류: `u` 를 (`u.f2` 를 통해) 한 번에 두 번 이상 가변으로 대여할 수 없습니다
fn test() {
let mut u = MyUnion { f1: 1 };
unsafe {
let b1 = &mut u.f1;
// ---- 첫 번째 가변 대여가 여기서 발생합니다 (`u.f1` 을 통해)
let b2 = &mut u.f2;
// ^^^^ 두 번째 가변 대여가 여기서 발생합니다 (`u.f2` 를 통해)
*b1 = 5;
}
// - 첫 번째 대여가 여기서 끝납니다
assert_eq!(unsafe { u.f1 }, 5);
}
}
보시다시피, 많은 측면(레이아웃, 안전성, 소유권 제외)에서 공용체는 구조체와 정확히 동일하게 동작하며, 이는 주로 구조체로부터 구문적 형태를 상속받았기 때문입니다. 이는 Rust 언어의 언급되지 않은 많은 측면(예: 비공개(privacy), 이름 해석, 타입 추론, 제네릭, 트레잇 구현, 고유 구현(inherent implementations), 일관성(coherence), 패턴 검사 등등)에서도 마찬가지입니다.
상수 아이템
Syntax
ConstantItem →
const ( IDENTIFIER | _ ) : Type ( = Expression )? ;
상수 아이템 은 프로그램의 특정 메모리 위치와 연관되지 않은 선택적으로 명명된 상수 값 입니다.
상수는 사용되는 모든 곳에 본질적으로 인라인(inlined)되므로, 사용될 때 관련 컨텍스트로 직접 복사됩니다. 여기에는 외부 크레이트의 상수 사용과, 비-Copy 타입의 상수가 포함됩니다. 동일한 상수에 대한 참조가 동일한 메모리 주소를 참조한다고 보장할 수는 없습니다.
상수 선언은 해당 모듈이나 블록의 값 네임스페이스 에 상수 값을 정의합니다.
상수는 명시적으로 타입을 지정해야 합니다. 타입은 'static 라이프타임을 가져야 합니다. 초기화 식의 모든 참조는 'static 라이프타임을 가져야 합니다. 상수 타입 내의 참조는 기본적으로 'static 라이프타임입니다. 정적 라이프타임 생략을 참조하십시오.
A reference to a constant will have 'static lifetime if the constant value is eligible for promotion; otherwise, a temporary will be created.
#![allow(unused)]
fn main() {
const BIT1: u32 = 1 << 0;
const BIT2: u32 = 1 << 1;
const BITS: [u32; 2] = [BIT1, BIT2];
const STRING: &'static str = "bitstring";
struct BitsNStrings<'a> {
mybits: [u32; 2],
mystring: &'a str,
}
const BITS_N_STRINGS: BitsNStrings<'static> = BitsNStrings {
mybits: BITS,
mystring: STRING,
};
}
상수 표현식은 트레잇 정의 에서만 생략될 수 있습니다.
Constants with destructors
상수는 소멸자를 포함할 수 있습니다. 소멸자는 값이 스코프를 벗어날 때 실행됩니다.
#![allow(unused)]
fn main() {
struct TypeWithDestructor(i32);
impl Drop for TypeWithDestructor {
fn drop(&mut self) {
println!("Dropped. Held {}.", self.0);
}
}
const ZERO_WITH_DESTRUCTOR: TypeWithDestructor = TypeWithDestructor(0);
fn create_and_drop_zero_with_destructor() {
let x = ZERO_WITH_DESTRUCTOR;
// x는 함수가 끝날 때 드롭되어 drop을 호출합니다.
// "Dropped. Held 0."을 출력합니다.
}
}
이름 없는 상수
연관 상수 와 달리, 자유(free) 상수는 이름 대신 밑줄을 사용하여 이름 없이 선언할 수 있습니다. 예를 들어:
#![allow(unused)]
fn main() {
const _: () = { struct _SameNameTwice; };
// 위와 같은 이름이지만 괜찮습니다:
const _: () = { struct _SameNameTwice; };
}
밑줄 임포트 와 마찬가지로, 매크로는 동일한 스코프에서 동일한 이름 없는 상수를 안전하게 두 번 이상 내보낼 수 있습니다. 예를 들어, 다음 코드는 오류를 생성하지 않아야 합니다:
#![allow(unused)]
fn main() {
macro_rules! m {
($item: item) => { $item $item }
}
m!(const _: () = (););
// 다음과 같이 확장됩니다:
// const _: () = ();
// const _: () = ();
}
평가
자유(Free) 상수는 항상 컴파일 타임에 평가되어 패닉을 드러냅니다. 이는 사용되지 않는 함수 내에서도 발생합니다:
#![allow(unused)]
fn main() {
// 컴파일 타임 패닉
const PANIC: () = std::unimplemented!();
fn unused_generic_function<T>() {
// 실패하는 컴파일 타임 어설션
const _: () = assert!(usize::BITS == 0);
}
}
정적 아이템
Syntax
StaticItem →
ItemSafety?1 static mut? IDENTIFIER : Type ( = Expression )? ;
A static item is similar to a constant, except that it represents an allocation in the program that is initialized with the initializer expression. All references and raw pointers to the static refer to the same allocation.
정적 아이템은 static 라이프타임을 가지며, 이는 Rust 프로그램의 다른 모든 라이프타임보다 오래 지속됩니다. 정적 아이템은 프로그램이 끝날 때 drop 을 호출하지 않습니다.
If the static has a size of at least 1 byte, this allocation is disjoint from all other such static allocations as well as heap allocations and stack-allocated variables. However, the storage of immutable static items can overlap with allocations that do not themselves have a unique address, such as promoteds and const items.
정적 선언은 해당 모듈이나 블록의 값 네임스페이스 에 정적 값을 정의합니다.
정적 초기화자는 컴파일 타임에 평가되는 상수 표현식입니다. 정적 초기화자는 다른 정적 아이템을 참조하고 읽을 수 있습니다. 가변 정적 아이템에서 읽을 때는 해당 정적 아이템의 초기 값을 읽습니다.
내부 가변성(interior mutable) 이 없는 타입을 포함하는 mut 가 아닌 정적 아이템은 읽기 전용 메모리에 배치될 수 있습니다.
정적 아이템에 대한 모든 접근은 안전하지만, 정적 아이템에는 몇 가지 제약 사항이 있습니다:
- 스레드 안전한 접근을 허용하려면 타입에
Sync트레잇 바운드가 있어야 합니다.
초기화 표현식은 외부 블록 에서는 생략되어야 하며, 자유(free) 정적 아이템의 경우에는 반드시 제공되어야 합니다.
safe 및 unsafe 한정자는 시맨틱적으로 외부 블록 에서 사용될 때만 허용됩니다.
정적 아이템과 제네릭
제네릭 스코프(예: blanket 또는 기본 구현)에 정의된 정적 아이템은 정적 정의가 현재 스코프 밖으로 나와 모듈로 이동한 것처럼 정확히 하나의 정적 아이템만 정의되는 결과를 낳습니다. 모노모르포화(monomorphization)마다 하나의 아이템이 생성되는 것은 아닙니다.
이 코드는:
use std::sync::atomic::{AtomicUsize, Ordering};
trait Tr {
fn default_impl() {
static COUNTER: AtomicUsize = AtomicUsize::new(0);
println!("default_impl: 카운터는 {}였습니다", COUNTER.fetch_add(1, Ordering::Relaxed));
}
fn blanket_impl();
}
struct Ty1 {}
struct Ty2 {}
impl<T> Tr for T {
fn blanket_impl() {
static COUNTER: AtomicUsize = AtomicUsize::new(0);
println!("blanket_impl: 카운터는 {}였습니다", COUNTER.fetch_add(1, Ordering::Relaxed));
}
}
fn main() {
<Ty1 as Tr>::default_impl();
<Ty2 as Tr>::default_impl();
<Ty1 as Tr>::blanket_impl();
<Ty2 as Tr>::blanket_impl();
}
다음과 같이 출력합니다:
default_impl: counter was 0
default_impl: counter was 1
blanket_impl: counter was 0
blanket_impl: counter was 1
가변 정적 아이템
If a static item is declared with the mut keyword, then it is allowed to be modified by the program. One of Rust’s goals is to make concurrency bugs hard to run into, and this is obviously a very large source of race conditions or other bugs.
이러한 이유로, 가변 정적 변수를 읽거나 쓸 때는 unsafe 블록이 필요합니다. 가변 정적 변수에 대한 수정이 동일한 프로세스에서 실행되는 다른 스레드에 대해 안전한지 주의해야 합니다.
하지만 가변 정적 아이템은 여전히 매우 유용합니다. C 라이브러리와 함께 사용할 수 있으며 extern 블록 내에서 C 라이브러리로부터 바인딩될 수도 있습니다.
#![allow(unused)]
fn main() {
fn atomic_add(_: *mut u32, _: u32) -> u32 { 2 }
static mut LEVELS: u32 = 0;
// 이것은 공유 상태가 없다는 아이디어를 위반하며, 내부적으로 경쟁 상태로부터
// 보호하지 않으므로, 이 함수는 `unsafe` 입니다
unsafe fn bump_levels_unsafe() -> u32 {
unsafe {
let ret = LEVELS;
LEVELS += 1;
return ret;
}
}
// `bump_levels_unsafe` 의 대안으로, 이전 값을 반환하는 atomic_add 함수가
// 있다고 가정할 때 이 함수는 안전합니다. 이 함수는 다른 코드가 비원자적(non-atomic)
// 방식으로 정적 변수에 접근하지 않는 경우에만 안전합니다. 만약 그러한 접근이 가능하다면
// (`bump_levels_unsafe` 에서처럼), 호출자에게 동시 접근을 여전히 막아야 함을
// 알리기 위해 `unsafe` 여야 합니다.
fn bump_levels_safe() -> u32 {
unsafe {
return atomic_add(&raw mut LEVELS, 1);
}
}
}
가변 정적 아이템은 타입이 Sync 트레잇을 구현하지 않아도 된다는 점을 제외하면 일반 정적 아이템과 동일한 제약 사항을 가집니다.
Using statics or consts
상수 아이템을 사용해야 할지 정적 아이템을 사용해야 할지 혼란스러울 수 있습니다. 다음 중 하나가 참이 아니라면 일반적으로 상수가 정적 아이템보다 선호되어야 합니다:
- 대량의 데이터를 저장하고 있다.
- 정적 아이템의 단일 주소 속성이 필요하다.
- 내부 가변성(interior mutability)이 필요하다.
-
safe및unsafe함수 한정자는 시맨틱적으로extern블록 내에서만 허용됩니다. ↩
트레잇
Syntax
Trait →
unsafe? trait IDENTIFIER GenericParams? ( : TypeParamBounds? )? WhereClause?
{
InnerAttribute*
AssociatedItem*
}
트레잇 은 타입이 구현할 수 있는 추상 인터페이스를 설명합니다. 이 인터페이스는 세 가지 종류의 연관 아이템 으로 구성됩니다:
트레잇 선언은 해당 모듈이나 블록의 타입 네임스페이스 에 트레잇을 정의합니다.
연관 아이템은 트레잇의 멤버로서 각각의 네임스페이스 내에 정의됩니다. 연관 타입은 타입 네임스페이스에 정의됩니다. 연관 상수와 연관 함수는 값 네임스페이스에 정의됩니다.
모든 트레잇은 “이 인터페이스를 구현하는 타입“을 참조하는 암시적 타입 매개변수 Self 를 정의합니다. 트레잇은 추가적인 타입 매개변수도 포함할 수 있습니다. Self 를 포함한 이러한 타입 매개변수는 일반적인 방식대로 다른 트레잇 등에 의해 제약될 수 있습니다.
트레잇은 별도의 구현 을 통해 특정 타입에 대해 구현됩니다.
트레잇 함수는 함수 본문을 세미콜론으로 대체하여 생략할 수 있습니다. 이는 구현에서 함수를 정의해야 함을 나타냅니다. 트레잇 함수가 본문을 정의하면, 이 정의는 이를 오버라이드하지 않는 구현에 대한 기본값으로 작동합니다. 마찬가지로, 연관 상수는 등호와 표현식을 생략하여 구현에서 상수 값을 정의해야 함을 나타낼 수 있습니다. 연관 타입은 타입을 절대 정의해서는 안 되며, 타입은 오직 구현에서만 지정될 수 있습니다.
#![allow(unused)]
fn main() {
// 정의가 있거나 없는 연관 트레잇 아이템의 예시.
trait Example {
const CONST_NO_DEFAULT: i32;
const CONST_WITH_DEFAULT: i32 = 99;
type TypeNoDefault;
fn method_without_default(&self);
fn method_with_default(&self) {}
}
}
트레잇 함수는 const 가 될 수 없습니다.
트레잇 바운드
제네릭 아이템은 타입 매개변수에 대한 바운드 로 트레잇을 사용할 수 있습니다.
제네릭 트레잇
타입 매개변수를 지정하여 트레잇을 제네릭으로 만들 수 있습니다. 이는 제네릭 함수에서 사용되는 것과 동일한 구문을 사용하여 트레잇 이름 뒤에 나타납니다.
#![allow(unused)]
fn main() {
trait Seq<T> {
fn len(&self) -> u32;
fn elt_at(&self, n: u32) -> T;
fn iter<F>(&self, f: F) where F: Fn(T);
}
}
Dyn 호환성
dyn 호환(dyn-compatible) 트레잇은 트레잇 객체 의 기본 트레잇이 될 수 있습니다. 트레잇은 다음 조건을 충족하면 dyn 호환 입니다:
- 모든 슈퍼트레잇 도 dyn 호환이어야 합니다.
Sized는 슈퍼트레잇 이 아니어야 합니다. 다시 말해,Self: Sized를 요구하지 않아야 합니다.
- 연관 상수를 갖지 않아야 합니다.
- 제네릭을 포함한 연관 타입을 갖지 않아야 합니다.
- 모든 연관 함수는 트레잇 객체에서 디스패치 가능하거나(dispatchable) 명시적으로 디스패치 불가능해야 합니다:
- 디스패치 가능한 함수는 다음 조건을 만족해야 합니다:
- 타입 매개변수를 갖지 않아야 합니다(라이프타임 매개변수는 허용됨).
- 수신자(receiver)의 타입 이외에는
Self를 사용하지 않는 메서드여야 합니다. - 다음 중 하나의 타입을 가진 수신자를 가져야 합니다:
- 불투명한(opaque) 반환 타입을 갖지 않아야 합니다. 즉,
async fn이 아니어야 합니다(숨겨진Future타입을 가짐).- 반환 위치
impl Trait타입을 갖지 않아야 합니다(fn example(&self) -> impl Trait).
where Self: Sized바운드를 갖지 않아야 합니다(Self수신자 타입(즉,self)은 이를 암시합니다).
- 명시적으로 디스패치 불가능한 함수는 다음을 요구합니다:
where Self: Sized바운드를 가져야 합니다(Self수신자 타입(즉,self)은 이를 암시합니다).
- 디스패치 가능한 함수는 다음 조건을 만족해야 합니다:
AsyncFn,AsyncFnMut, 및AsyncFnOnce트레잇은 dyn 호환이 아닙니다.
Note
This concept was formerly known as object safety.
#![allow(unused)]
fn main() {
use std::rc::Rc;
use std::sync::Arc;
use std::pin::Pin;
// dyn 호환 메서드의 예시.
trait TraitMethods {
fn by_ref(self: &Self) {}
fn by_ref_mut(self: &mut Self) {}
fn by_box(self: Box<Self>) {}
fn by_rc(self: Rc<Self>) {}
fn by_arc(self: Arc<Self>) {}
fn by_pin(self: Pin<&Self>) {}
fn with_lifetime<'a>(self: &'a Self) {}
fn nested_pin(self: Pin<Arc<Self>>) {}
}
struct S;
impl TraitMethods for S {}
let t: Box<dyn TraitMethods> = Box::new(S);
}
#![allow(unused)]
fn main() {
// 이 트레잇은 dyn 호환이지만, 이 메서드들은 트레잇 객체에서 디스패치될 수 없습니다.
trait NonDispatchable {
// 비-메서드(Non-methods)는 디스패치될 수 없습니다.
fn foo() where Self: Sized {}
// Self 타입은 런타임까지 알 수 없습니다.
fn returns(&self) -> Self where Self: Sized;
// `other` 는 수신자와 다른 구체적인 타입일 수 있습니다.
fn param(&self, other: Self) where Self: Sized {}
// 제네릭은 vtable과 호환되지 않습니다.
fn typed<T>(&self, x: T) where Self: Sized {}
}
struct S;
impl NonDispatchable for S {
fn returns(&self) -> Self where Self: Sized { S }
}
let obj: Box<dyn NonDispatchable> = Box::new(S);
obj.returns(); // 오류: Self 반환으로 호출할 수 없음
obj.param(S); // 오류: Self 매개변수로 호출할 수 없음
obj.typed(1); // 오류: 제네릭 타입으로 호출할 수 없음
}
#![allow(unused)]
fn main() {
use std::rc::Rc;
// dyn 호환이 아닌 트레잇의 예시.
trait DynIncompatible {
const CONST: i32 = 1; // 오류: 연관 상수를 가질 수 없음
fn foo() {} // 오류: Sized 없는 연관 함수
fn returns(&self) -> Self; // 오류: 반환 타입에 `Self` 가 있음
fn typed<T>(&self, x: T) {} // 오류: 제네릭 타입 매개변수가 있음
fn nested(self: Rc<Box<Self>>) {} // ERROR: nested receiver cannot be dispatched on
}
struct S;
impl DynIncompatible for S {
fn returns(&self) -> Self { S }
}
let obj: Box<dyn DynIncompatible> = Box::new(S); // 오류
}
#![allow(unused)]
fn main() {
// `Self: Sized` 트레잇은 dyn 호환이 아닙니다.
trait TraitWithSize where Self: Sized {}
struct S;
impl TraitWithSize for S {}
let obj: Box<dyn TraitWithSize> = Box::new(S); // 오류
}
#![allow(unused)]
fn main() {
// `Self` 가 타입 인수인 경우 dyn 호환이 아닙니다.
trait Super<A> {}
trait WithSelf: Super<Self> where Self: Sized {}
struct S;
impl<A> Super<A> for S {}
impl WithSelf for S {}
let obj: Box<dyn WithSelf> = Box::new(S); // 오류: `Self` 타입 매개변수를 사용할 수 없음
}
슈퍼트레잇
슈퍼트레잇(Supertraits) 은 특정 트레잇을 구현하기 위해 타입이 반드시 구현해야 하는 트레잇입니다. 또한, 제네릭 이나 트레잇 객체 가 트레잇으로 바운딩된 곳이면 어디서나 그 슈퍼트레잇의 연관 아이템에 접근할 수 있습니다.
슈퍼트레잇은 트레잇의 Self 타입에 대한 트레잇 바운드와, 해당 트레잇 바운드에 선언된 트레잇의 슈퍼트레잇을 통해 추이적으로 선언됩니다. 트레잇이 자기 자신의 슈퍼트레잇이 되는 것은 오류입니다.
슈퍼트레잇을 가진 트레잇을 그 슈퍼트레잇의 서브트레잇(subtrait) 이라고 합니다.
다음은 Shape 를 Circle 의 슈퍼트레잇으로 선언하는 예입니다.
#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle: Shape { fn radius(&self) -> f64; }
}
그리고 다음은 Where 절 을 사용한 것을 제외하고는 동일한 예입니다.
#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle where Self: Shape { fn radius(&self) -> f64; }
}
다음 예제는 Shape 의 area 함수를 사용하여 radius 에 기본 구현을 제공합니다.
#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle where Self: Shape {
fn radius(&self) -> f64 {
// A = pi * r^2
// 따라서 대수적으로,
// r = sqrt(A / pi)
(self.area() / std::f64::consts::PI).sqrt()
}
}
}
다음 예제는 제네릭 매개변수에서 슈퍼트레잇 메서드를 호출합니다.
#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle: Shape { fn radius(&self) -> f64; }
fn print_area_and_radius<C: Circle>(c: C) {
// 여기서 우리는 `Circle` 의 슈퍼트레잇 `Shape` 의 area 메서드를 호출합니다.
println!("면적: {}", c.area());
println!("반지름: {}", c.radius());
}
}
마찬가지로, 다음은 트레잇 객체에서 슈퍼트레잇 메서드를 호출하는 예입니다.
#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle: Shape { fn radius(&self) -> f64; }
struct UnitCircle;
impl Shape for UnitCircle { fn area(&self) -> f64 { std::f64::consts::PI } }
impl Circle for UnitCircle { fn radius(&self) -> f64 { 1.0 } }
let circle = UnitCircle;
let circle = Box::new(circle) as Box<dyn Circle>;
let nonsense = circle.radius() * circle.area();
}
Unsafe 트레잇
unsafe 키워드로 시작하는 트레잇 아이템은 해당 트레잇을 구현 하는 것이 안전하지 않을 수 있음을 나타냅니다. 올바르게 구현된 unsafe 트레잇을 사용하는 것은 안전합니다. 트레잇 구현 역시 unsafe 키워드로 시작해야 합니다.
Sync 와 Send는 unsafe 트레잇의 예입니다.
매개변수 패턴
Parameters in associated functions without a body only allow IDENTIFIER or _ wild card patterns, as well as the form allowed by SelfParam. mut IDENTIFIER is currently allowed, but it is deprecated and will become a hard error in the future.
#![allow(unused)]
fn main() {
trait T {
fn f1(&self);
fn f2(x: Self, _: i32);
}
}
#![allow(unused)]
fn main() {
trait T {
fn f2(&x: &i32); // ERROR: patterns aren't allowed in functions without bodies
}
}
Parameters in associated functions with a body only allow irrefutable patterns.
#![allow(unused)]
fn main() {
trait T {
fn f1((a, b): (i32, i32)) {} // OK: is irrefutable
}
}
#![allow(unused)]
fn main() {
trait T {
fn f1(123: i32) {} // ERROR: pattern is refutable
fn f2(Some(x): Option<i32>) {} // ERROR: pattern is refutable
}
}
2018 Edition differences
Prior to the 2018 edition, the pattern for an associated function parameter is optional:
#![allow(unused)] fn main() { // 2015 에디션 trait T { fn f(i32); // OK: parameter identifiers are not required } }Beginning in the 2018 edition, patterns are no longer optional.
2018 Edition differences
Prior to the 2018 edition, parameters in associated functions with a body are limited to the following kinds of patterns:
- IDENTIFIER
mutIDENTIFIER_&IDENTIFIER&&IDENTIFIER#![allow(unused)] fn main() { // 2015 에디션 trait T { fn f1((a, b): (i32, i32)) {} // ERROR: pattern not allowed } }Beginning in 2018, all irrefutable patterns are allowed as described in items.traits.params.patterns-with-body.
아이템 가시성
Trait items syntactically allow a Visibility annotation, but this is rejected when the trait is validated. This allows items to be parsed with a unified syntax across different contexts where they are used. As an example, an empty vis macro fragment specifier can be used for trait items, where the macro rule may be used in other situations where visibility is allowed.
macro_rules! create_method {
($vis:vis $name:ident) => {
$vis fn $name(&self) {}
};
}
trait T1 {
// 빈 `vis` 는 허용됩니다.
create_method! { method_of_t1 }
}
struct S;
impl S {
// 여기서 가시성은 허용됩니다.
create_method! { pub method_of_s }
}
impl T1 for S {}
fn main() {
let s = S;
s.method_of_t1();
s.method_of_s();
}
구현
Syntax
Implementation → InherentImpl | TraitImpl
InherentImpl →
impl GenericParams? Type WhereClause? {
InnerAttribute*
AssociatedItem*
}
TraitImpl →
unsafe? impl GenericParams? !? TypePath for Type
WhereClause?
{
InnerAttribute*
AssociatedItem*
}
구현(implementation) 은 아이템을 구현 타입(implementing type) 과 연관시키는 아이템입니다. 구현은 impl 키워드로 정의되며, 구현되는 타입의 인스턴스에 속하거나 타입에 정적으로 속하는 함수들을 포함합니다.
구현에는 두 가지 유형이 있습니다.
- 고유 구현(inherent implementations)
- 트레잇(trait) 구현
Inherent implementations
고유 구현은 impl 키워드, 제네릭 타입 선언, 명목상 타입(nominal type)으로의 경로, Where 절, 그리고 중괄호로 묶인 연관 가능한 아이템 집합의 시퀀스로 정의됩니다.
이 명목상 타입을 구현 타입 이라고 하며, 연관 가능한 아이템들은 구현 타입에 대한 연관 아이템(associated items) 입니다.
고유 구현은 포함된 아이템들을 구현 타입과 연관시킵니다.
고유 구현은 연관 함수(메서드 포함)와 연관 상수를 포함할 수 있습니다.
연관 타입 별칭은 포함할 수 없습니다.
연관 아이템으로의 경로 는 구현 타입으로의 임의의 경로 뒤에, 마지막 경로 구성 요소로서 연관 아이템의 식별자가 오는 형태입니다.
하나의 타입은 여러 개의 고유 구현을 가질 수도 있습니다. 구현 타입은 반드시 원본 타입 정의와 동일한 크레이트 내에서 정의되어야 합니다.
pub mod color {
pub struct Color(pub u8, pub u8, pub u8);
impl Color {
pub const WHITE: Color = Color(255, 255, 255);
}
}
mod values {
use super::color::Color;
impl Color {
pub fn red() -> Color {
Color(255, 0, 0)
}
}
}
pub use self::color::Color;
fn main() {
// 같은 모듈 내의 구현 타입과 구현에 대한 실제 경로.
color::Color::WHITE;
// 서로 다른 모듈에 있는 구현 블록들도 여전히 타입을 통한 경로로 접근됩니다.
color::Color::red();
// 구현 타입에 대해 다시 내보내기(re-exported)된 경로들도 작동합니다.
Color::red();
// 작동하지 않습니다. `values` 내의 사용(use)이 pub이 아니기 때문입니다.
// values::Color::red();
}
Trait implementations
트레잇 구현 은 고유 구현과 비슷하게 정의되지만, 선택적인 제네릭 타입 선언 뒤에 트레잇 이 오고, 그 뒤에 for 키워드와 명목상 타입으로의 경로가 온다는 점이 다릅니다.
이 트레잇을 구현된 트레잇 이라고 합니다. 구현 타입은 구현된 트레잇을 구현합니다.
트레잇 구현은 구현된 트레잇에 선언된 기본값이 없는 모든 연관 아이템을 정의해야 하며, 구현된 트레잇에 정의된 기본 연관 아이템을 재정의할 수 있습니다. 그 외의 다른 아이템은 정의할 수 없습니다.
연관 아이템으로의 경로는, 경로 구성 요소로서 < 뒤에 구현 타입 경로, 그 뒤에 as, 그 뒤에 트레잇 경로, 그 뒤에 > 가 오고, 그 다음에 연관 아이템의 경로 구성 요소가 오는 형태입니다.
Unsafe 트레잇 은 트레잇 구현이 unsafe 키워드로 시작할 것을 요구합니다.
#![allow(unused)]
fn main() {
#[derive(Copy, Clone)]
struct Point {x: f64, y: f64};
type Surface = i32;
struct BoundingBox {x: f64, y: f64, width: f64, height: f64};
trait Shape { fn draw(&self, s: Surface); fn bounding_box(&self) -> BoundingBox; }
fn do_draw_circle(s: Surface, c: Circle) { }
struct Circle {
radius: f64,
center: Point,
}
impl Copy for Circle {}
impl Clone for Circle {
fn clone(&self) -> Circle { *self }
}
impl Shape for Circle {
fn draw(&self, s: Surface) { do_draw_circle(s, *self); }
fn bounding_box(&self) -> BoundingBox {
let r = self.radius;
BoundingBox {
x: self.center.x - r,
y: self.center.y - r,
width: 2.0 * r,
height: 2.0 * r,
}
}
}
}
Trait implementation coherence
트레잇 구현은 고아 규칙(orphan rules) 검사에 실패하거나 중복되는 구현 인스턴스가 있는 경우 일관성이 없는(incoherent) 것으로 간주됩니다.
Two trait implementations overlap when there is a non-empty intersection of the traits the implementation is for, the implementations can be instantiated with the same type.
고아 규칙
The orphan rule states that a trait implementation is only allowed if either the trait or at least one of the types in the implementation is defined in the current crate. It prevents conflicting trait implementations across different crates and is key to ensuring coherence.
An orphan implementation is one that implements a foreign trait for a foreign type. If these were freely allowed, two crates could implement the same trait for the same type in incompatible ways, creating a situation where adding or updating a dependency could break compilation due to conflicting implementations.
The orphan rule enables library authors to add new implementations to their traits without fear that they’ll break downstream code. Without these restrictions, a library couldn’t add an implementation like impl<T: Display> MyTrait for T without potentially conflicting with downstream implementations.
impl<P1..=Pn> Trait<T1..=Tn> for T0 가 주어졌을 때, impl 은 다음 중 최소 하나가 참인 경우에만 유효합니다.
Trait가 로컬 트레잇 인 경우- 다음 모두를 만족하는 경우
- 타입
T0..=Tn중 적어도 하나는 로컬 타입 이어야 합니다. 이러한 첫 번째 타입을Ti라고 합시다. T0..Ti(Ti제외)에는 덮이지 않은 타입(uncovered type) 매개변수P1..=Pn이 나타날 수 없습니다.
- 타입
덮이지 않은 타입 매개변수의 출현만이 제한됩니다.
일관성을 위해 기초 타입(fundamental types) 은 특별하게 취급된다는 점에 유의하세요. Box<T> 에서 T 는 덮인 것으로 간주되지 않으며, Box<LocalType> 은 로컬로 간주됩니다.
Generic implementations
구현은 제네릭 매개변수 를 가질 수 있으며, 이는 구현의 나머지 부분에서 사용될 수 있습니다. 구현 매개변수는 impl 키워드 바로 뒤에 작성됩니다.
#![allow(unused)]
fn main() {
trait Seq<T> { fn dummy(&self, _: T) { } }
impl<T> Seq<T> for Vec<T> {
/* ... */
}
impl Seq<bool> for u32 {
/* 정수를 비트 시퀀스로 취급 */
}
}
제네릭 매개변수가 다음 중 하나에 한 번이라도 나타나면 해당 구현을 제약(constrain) 합니다.
타입 및 상수(const) 매개변수는 반드시 구현을 제약해야 합니다. 라이프타임은 연관 타입에서 사용되는 경우 반드시 구현을 제약해야 합니다.
제약하는 상황의 예시:
#![allow(unused)]
fn main() {
trait Trait{}
trait GenericTrait<T> {}
trait HasAssocType { type Ty; }
struct Struct;
struct GenericStruct<T>(T);
struct ConstGenericStruct<const N: usize>([(); N]);
// T는 GenericTrait의 인수가 됨으로써 제약합니다.
impl<T> GenericTrait<T> for i32 { /* ... */ }
// T는 GenericStruct의 인수가 됨으로써 제약합니다.
impl<T> Trait for GenericStruct<T> { /* ... */ }
// 마찬가지로, N은 ConstGenericStruct의 인수가 됨으로써 제약합니다.
impl<const N: usize> Trait for ConstGenericStruct<N> { /* ... */ }
// T는 트레잇을 제약하는 제네릭 매개변수인 타입 `U` 에 대한 바운드의
// 연관 타입 안에 있음으로써 제약합니다.
impl<T, U> GenericTrait<U> for u32 where U: HasAssocType<Ty = T> { /* ... */ }
// 이전과 비슷하지만, 타입이 `(U, isize)` 라는 점이 다릅니다. `U` 는 `T` 를 포함하는
// 타입 내부에 나타나며, 타입 자체는 아닙니다.
impl<T, U> GenericStruct<U> where (U, isize): HasAssocType<Ty = T> { /* ... */ }
}
제약하지 않는 상황의 예시:
#![allow(unused)]
fn main() {
// The rest of these are errors, since they have type or const parameters that
// do not constrain.
// T는 전혀 나타나지 않으므로 제약하지 않습니다.
impl<T> Struct { /* ... */ }
// N도 같은 이유로 제약하지 않습니다.
impl<const N: usize> Struct { /* ... */ }
// 구현 내부에서의 T의 사용은 구현을 제약하지 않습니다.
impl<T> Struct {
fn uses_t(t: &T) { /* ... */ }
}
// T는 U에 대한 바운드의 연관 타입으로 사용되지만, U가 제약하지 않습니다.
impl<T, U> Struct where U: HasAssocType<Ty = T> { /* ... */ }
// T는 바운드에서 사용되지만, 연관 타입으로서가 아니므로 제약하지 않습니다.
impl<T, U> GenericTrait<U> for u32 where U: GenericTrait<T> {}
}
허용되는, 제약하지 않는 라이프타임 매개변수의 예:
#![allow(unused)]
fn main() {
struct Struct;
impl<'a> Struct {}
}
허용되지 않는, 제약하지 않는 라이프타임 매개변수의 예:
#![allow(unused)]
fn main() {
struct Struct;
trait HasAssocType { type Ty; }
impl<'a> HasAssocType for Struct {
type Ty = &'a Struct;
}
}
Attributes on implementations
구현은 impl 키워드 앞에 외부 속성 을, 연관 아이템을 포함하는 중괄호 안에 내부 속성 을 가질 수 있습니다. 내부 속성은 반드시 모든 연관 아이템보다 먼저 와야 합니다. 여기서 의미를 갖는 속성은 cfg, deprecated, doc, 그리고 린트 검사 속성입니다.
외부 블록
Syntax
ExternBlock →
unsafe?1 extern Abi? {
InnerAttribute*
ExternalItem*
}
ExternalItem →
OuterAttribute* (
MacroInvocationSemi
| Visibility? StaticItem
| Visibility? Function
)
외부 블록은 현재 크레이트에서 정의 되지 않은 아이템의 선언 을 제공하며, Rust의 외부 함수 인터페이스(foreign function interface)의 기초가 됩니다. 이들은 검사되지 않은 임포트(unchecked imports)와 유사합니다.
외부 블록에서는 두 가지 종류의 아이템 선언 이 허용됩니다: 함수 와 정적 아이템(statics) 입니다.
Calling unsafe functions or accessing unsafe statics that are declared in external blocks is only allowed in an unsafe context.
외부 블록은 해당 모듈이나 블록의 값 네임스페이스 에 함수와 정적 아이템을 정의합니다.
unsafe 키워드는 외부 블록의 extern 키워드 앞에 나타나도록 시맨틱적으로 요구됩니다.
2024 Edition differences
Prior to the 2024 edition, the
unsafekeyword is optional. Thesafeandunsafeitem qualifiers are only allowed if the external block itself is marked asunsafe.
함수
외부 블록 내의 함수는 다른 Rust 함수와 동일한 방식으로 선언되지만, 본문을 가질 수 없으며 대신 세미콜론으로 끝난다는 점이 다릅니다.
Patterns are not allowed in parameters, only IDENTIFIER or _ may be used.
safe 및 unsafe 함수 한정자는 허용되지만, 다른 함수 한정자(예: const, async, extern)는 허용되지 않습니다.
외부 블록 내의 함수는 Rust에서 정의된 함수와 마찬가지로 Rust 코드에서 호출될 수 있습니다. Rust 컴파일러는 Rust ABI와 외부 ABI 간의 변환을 자동으로 처리합니다.
외부 블록에 선언된 함수는 safe 함수 한정자가 없는 한 암시적으로 unsafe 입니다.
When coerced to a function pointer, a function declared in an extern block has type extern "abi" for<'l1, ..., 'lm> fn(A1, ..., An) -> R, where 'l1, … 'lm are its lifetime parameters, A1, …, An are the declared types of its parameters, and R is the declared return type.
정적 아이템
외부 블록 내의 정적 아이템은 외부 블록 밖의 정적 아이템 과 동일한 방식으로 선언되지만, 값을 초기화하는 표현식을 갖지 않는다는 점이 다릅니다.
외부 블록에 선언된 정적 아이템이 safe 로 한정되지 않는 한, 해당 아이템에 접근하는 것은 가변성 여부와 상관없이 unsafe 입니다. 왜냐하면 임의의 (예: C) 코드가 정적 아이템의 초기화를 담당하므로, 해당 메모리의 비트 패턴이 선언된 타입에 대해 유효하다는 보장이 없기 때문입니다.
외부 정적 아이템은 외부 블록 밖의 정적 아이템 과 마찬가지로 불변이거나 가변일 수 있습니다.
불변 정적 아이템은 어떠한 Rust 코드가 실행되기 전에 반드시 초기화되어야 합니다. Rust 코드가 그것을 읽기 전에 초기화되는 것만으로는 충분하지 않습니다. 일단 Rust 코드가 실행되면, 불변 정적 아이템을 (Rust 내부에서든 외부에서든) 수정하는 것은 UnsafeCell 내부의 바이트에 대한 수정이 아닌 한 정의되지 않은 동작(UB)입니다.
ABI
The extern keyword can be followed by an optional ABI string. The ABI specifies the calling convention of the functions in the block. The calling convention defines a low-level interface for functions, such as how arguments are placed in registers or on the stack, how return values are passed, and who is responsible for cleaning up the stack.
Example
#![allow(unused)] fn main() { // Interface to the Windows API. unsafe extern "system" { /* ... */ } }
If the ABI string is not specified, it defaults to "C".
Note
The
externsyntax without an explicit ABI is being phased out, so it’s better to always write the ABI explicitly.For more details, see Rust issue #134986.
The following ABI strings are supported on all platforms:
unsafe extern "Rust"— The native calling convention for Rust functions and closures. This is the default when a function is declared without usingextern fn. The Rust ABI offers no stability guarantees.
unsafe extern "C"— The “C” ABI matches the default ABI chosen by the dominant C compiler for the target.
-
unsafe extern "system"— This is equivalent toextern "C"except on Windows x86_32 where it is equivalent to"stdcall"for non-variadic functions, and equivalent to"C"for variadic functions.Note
As the correct underlying ABI on Windows is target-specific, it’s best to use
extern "system"when attempting to link Windows API functions that don’t use an explicitly defined ABI.
extern "C-unwind"andextern "system-unwind"— Identical to"C"and"system", respectively, but with different behavior when the callee unwinds (by panicking or throwing a C++ style exception).
플랫폼별로 특화된 ABI 문자열들도 있습니다.
-
unsafe extern "cdecl"— The calling convention typically used with x86_32 C code.- Only available on x86_32 targets.
- Corresponds to MSVC’s
__cdecland GCC and clang’s__attribute__((cdecl)).
-
unsafe extern "stdcall"— The calling convention typically used by the Win32 API on x86_32.- Only available on x86_32 targets.
- Corresponds to MSVC’s
__stdcalland GCC and clang’s__attribute__((stdcall)).
-
unsafe extern "win64"— The Windows x64 ABI.- Only available on x86_64 targets.
- “win64” is the same as the “C” ABI on Windows x86_64 targets.
- Corresponds to GCC and clang’s
__attribute__((ms_abi)).
-
unsafe extern "sysv64"— The System V ABI.- Only available on x86_64 targets.
- “sysv64” is the same as the “C” ABI on non-Windows x86_64 targets.
- Corresponds to GCC and clang’s
__attribute__((sysv_abi)).
-
unsafe extern "aapcs"— The soft-float ABI for ARM.- Only available on ARM32 targets.
- “aapcs” is the same as the “C” ABI on soft-float ARM32.
- Corresponds to clang’s
__attribute__((pcs("aapcs"))).
Note
For details, see:
-
unsafe extern "fastcall"— A “fast” variant of stdcall that passes some arguments in registers.- Only available on x86_32 targets.
- Corresponds to MSVC’s
__fastcalland GCC and clang’s__attribute__((fastcall)).
-
unsafe extern "thiscall"— The calling convention typically used on C++ class member functions on x86_32 MSVC.- Only available on x86_32 targets.
- Corresponds to MSVC’s
__thiscalland GCC and clang’s__attribute__((thiscall)).
unsafe extern "efiapi"— The ABI used for UEFI functions.- Only available on x86 and ARM targets (32bit and 64bit).
Like "C" and "system", most platform-specific ABI strings also have a corresponding -unwind variant; specifically, these are:
"aapcs-unwind""cdecl-unwind""fastcall-unwind""stdcall-unwind""sysv64-unwind""thiscall-unwind""win64-unwind"
가변 인자 함수
외부 블록 내의 함수는 마지막 인수로 ... 을 지정하여 가변 인자 함수가 될 수 있습니다. 가변 인자 매개변수는 선택적으로 식별자를 가질 수 있습니다.
#![allow(unused)]
fn main() {
unsafe extern "C" {
unsafe fn foo(...);
unsafe fn bar(x: i32, ...);
unsafe fn with_name(format: *const u8, args: ...);
// SAFETY: This function guarantees it will not access
// variadic arguments.
safe fn ignores_variadic_arguments(x: i32, ...);
}
}
Warning
The
safequalifier should not be used on a function in anexternblock unless that function guarantees that it will not access the variadic arguments at all. Passing an unexpected number of arguments or arguments of unexpected type to a variadic function may lead to undefined behavior.
Variadic parameters can only be specified within extern blocks with the following ABI strings or their corresponding -unwind variants:
"aapcs""C""cdecl""efiapi""system""sysv64""win64"
외부 블록의 속성
다음 속성들 은 외부 블록의 동작을 제어합니다.
link 속성
link 속성 은 extern 블록 내의 아이템들을 위해 컴파일러가 링크해야 할 네이티브 라이브러리의 이름을 지정합니다.
It uses the MetaListNameValueStr syntax to specify its inputs. The name key is the name of the native library to link. The kind key is an optional value which specifies the kind of library with the following possible values:
dylib— 동적 라이브러리임을 나타냅니다.kind가 지정되지 않은 경우의 기본값입니다.
static— 정적 라이브러리임을 나타냅니다.
framework— macOS 프레임워크임을 나타냅니다. macOS 타겟인 경우에만 유효합니다.
raw-dylib— 컴파일러가 링크를 위해 임포트 라이브러리를 생성할 동적 라이브러리임을 나타냅니다(자세한 내용은 아래의dylib대raw-dylib섹션을 참조하세요). Windows 타겟인 경우에만 유효합니다.
kind 가 지정된 경우 name 키가 반드시 포함되어야 합니다.
선택적인 modifiers 인수는 링크할 라이브러리에 대해 링크 수정자(linking modifiers)를 지정하는 방법입니다.
수정자들은 쉼표로 구분된 문자열로 지정하며, 각 수정자 앞에 + 또는 - 를 붙여서 각각 해당 수정자의 활성화 또는 비활성화 여부를 나타냅니다.
Specifying multiple modifiers arguments in a single link attribute, or multiple identical modifiers in the same modifiers argument is not currently supported. Example: #[link(name = "mylib", kind = "static", modifiers = "+whole-archive")].
wasm_import_module 키는 호스트 환경으로부터 심볼을 임포트할 때 extern 블록 내 아이템들에 대한 WebAssembly 모듈 이름을 지정하는 데 사용될 수 있습니다. wasm_import_module 이 지정되지 않은 경우 기본 모듈 이름은 env 입니다.
#[link(name = "crypto")]
unsafe extern {
// …
}
#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern {
// …
}
#[link(wasm_import_module = "foo")]
unsafe extern {
// …
}
비어 있는 외부 블록에 link 속성을 추가하는 것도 유효합니다. 이를 통해 각 외부 블록마다 속성을 추가하는 대신, 코드의 다른 부분(업스트림 크레이트 포함)에 있는 외부 블록들의 링크 요구 사항을 충족시킬 수 있습니다.
링크 수정자: bundle
이 수정자는 static 링크 종류와만 호환됩니다. 다른 종류를 사용하면 컴파일러 오류가 발생합니다.
rlib나 staticlib을 빌드할 때 +bundle 은 네이티브 정적 라이브러리가 rlib나 staticlib 아카이브에 포함되고, 나중에 최종 바이너리를 링크할 때 그곳에서 추출된다는 것을 의미합니다.
When building a rlib -bundle means that the native static library is registered as a dependency of that rlib “by name”, and object files from it are included only during linking of the final binary, the file search by that name is also performed during final linking. When building a staticlib -bundle means that the native static library is simply not included into the archive and some higher level build system will need to add it later during linking of the final binary.
이 수정자는 실행 파일이나 동적 라이브러리와 같은 다른 타겟을 빌드할 때는 아무런 효과가 없습니다.
이 수정자의 기본값은 +bundle 입니다.
이 수정자에 대한 더 자세한 구현 세부 사항은 rustc의 bundle 문서에서 확인할 수 있습니다.
링크 수정자: whole-archive
이 수정자는 static 링크 종류와만 호환됩니다. 다른 종류를 사용하면 컴파일러 오류가 발생합니다.
+whole-archive 는 정적 라이브러리가 객체 파일을 하나도 버리지 않고 아카이브 전체로서 링크된다는 것을 의미합니다.
이 수정자의 기본값은 -whole-archive 입니다.
이 수정자에 대한 더 자세한 구현 세부 사항은 rustc의 whole-archive 문서에서 확인할 수 있습니다.
링크 수정자: verbatim
이 수정자는 모든 링크 종류와 호환됩니다.
+verbatim 은 rustc가 라이브러리 이름에 타겟별 라이브러리 접두사나 접미사(lib 또는 .a 등)를 직접 추가하지 않으며, 링커에게도 동일하게 요청하도록 최선을 다한다는 것을 의미합니다.
-verbatim 은 rustc가 라이브러리 이름을 링커에 전달하기 전에 타겟별 접두사와 접미사를 추가하거나, 링커가 암시적으로 이를 추가하는 것을 막지 않는다는 것을 의미합니다.
이 수정자의 기본값은 -verbatim 입니다.
이 수정자에 대한 더 자세한 구현 세부 사항은 rustc의 verbatim 문서에서 확인할 수 있습니다.
dylib 대 raw-dylib
Windows에서 동적 라이브러리에 링크하려면 링커에 임포트 라이브러리(import library)를 제공해야 합니다. 이것은 동적 라이브러리가 내보내는 모든 심볼을 선언하여, 링커가 해당 심볼들이 런타임에 동적으로 로드되어야 함을 알 수 있게 해주는 특별한 정적 라이브러리입니다.
kind = "dylib" 을 지정하면 Rust 컴파일러가 name 키에 기반한 임포트 라이브러리를 링크하도록 지시합니다. 그러면 링커는 일반적인 라이브러리 해석 로직을 사용하여 해당 임포트 라이브러리를 찾습니다. 반면에, kind = "raw-dylib" 을 지정하면 컴파일러가 컴파일 중에 직접 임포트 라이브러리를 생성하여 링커에 제공하도록 지시합니다.
raw-dylib 은 Windows에서만 지원됩니다. 다른 플랫폼을 타겟으로 할 때 사용하면 컴파일러 오류가 발생합니다.
import_name_type 키
x86 Windows에서 함수 이름은 호출 규약(calling convention)을 나타내기 위해 “장식(decorated)“됩니다(즉, 특정 접두사나 접미사가 추가됩니다). 예를 들어, 인수가 없는 fn1 이라는 이름의 stdcall 호출 규약 함수는 _fn1@0 으로 장식됩니다. 하지만 PE 포맷 은 접두사가 없거나 장식되지 않은 이름도 허용합니다. 또한 MSVC와 GNU 툴체인은 동일한 호출 규약에 대해 서로 다른 장식을 사용하므로, 기본적으로 일부 Win32 함수들은 GNU 툴체인을 통해 raw-dylib 링크 종류를 사용하여 호출할 수 없습니다.
이러한 차이점을 해결하기 위해, raw-dylib 링크 종류를 사용할 때 import_name_type 키에 다음 값 중 하나를 지정하여 생성된 임포트 라이브러리 내 함수 이름 지정 방식을 변경할 수 있습니다.
decorated: 함수 이름이 MSVC 툴체인 형식을 사용하여 완전히 장식됩니다.noprefix: 함수 이름이 MSVC 툴체인 형식을 사용하여 장식되지만, 앞의?,@또는 선택적으로_를 생략합니다.undecorated: 함수 이름이 장식되지 않습니다.
import_name_type 키가 지정되지 않으면, 함수 이름은 타겟 툴체인의 형식을 사용하여 완전히 장식됩니다.
변수는 절대 장식되지 않으므로, import_name_type 키는 생성된 임포트 라이브러리 내 변수 이름에 아무런 영향을 주지 않습니다.
import_name_type 키는 x86 Windows에서만 지원됩니다. 다른 플랫폼을 타겟으로 할 때 사용하면 컴파일러 오류가 발생합니다.
link_name 속성
The link_name attribute may be applied to declarations inside an extern block to specify the symbol to import for the given function or static.
Example
#![allow(unused)] fn main() { unsafe extern "C" { #[link_name = "actual_symbol_name"] safe fn name_in_rust(); } }
The link_name attribute uses the MetaNameValueStr syntax.
The link_name attribute may only be applied to a function or static item in an extern block.
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
Only the last use of link_name on an item has effect.
Note
rustclints against any use preceding the last. This may become an error in the future.
The link_name attribute may not be used with the link_ordinal attribute.
link_ordinal 속성
link_ordinal 속성 은 extern 블록 내의 선언에 적용되어, 링크할 임포트 라이브러리를 생성할 때 사용할 숫자 오디널(ordinal)을 나타낼 수 있습니다. 오디널은 Windows의 동적 라이브러리에서 내보내는 심볼마다 부여되는 고유한 번호이며, 라이브러리가 로드될 때 이름을 검색하는 대신 이 번호를 사용하여 심볼을 찾는 데 사용될 수 있습니다.
Warning
link_ordinalshould only be used in cases where the ordinal of the symbol is known to be stable: if the ordinal of a symbol is not explicitly set when its containing binary is built then one will be automatically assigned to it, and that assigned ordinal may change between builds of the binary.
#![allow(unused)]
fn main() {
#[cfg(all(windows, target_arch = "x86"))]
#[link(name = "exporter", kind = "raw-dylib")]
unsafe extern "stdcall" {
#[link_ordinal(15)]
safe fn imported_function_stdcall(i: i32);
}
}
이 속성은 오직 raw-dylib 링크 종류와 함께 사용됩니다. 다른 종류를 사용하면 컴파일러 오류가 발생합니다.
이 속성을 link_name 속성과 함께 사용하면 컴파일러 오류가 발생합니다.
함수 매개변수의 속성
외부 함수의 매개변수에 붙는 속성들은 일반 함수 매개변수 와 동일한 규칙 및 제약 사항을 따릅니다.
-
2024 에디션부터는
unsafe키워드가 시맨틱적으로 요구됩니다. ↩
제네릭 파라미터
Syntax
GenericParams → < ( GenericParam ( , GenericParam )* ,? )? >
GenericParam → OuterAttribute* ( LifetimeParam | TypeParam | ConstParam )
LifetimeParam → Lifetime ( : LifetimeBounds )?
TypeParam → IDENTIFIER ( : TypeParamBounds? )? ( = Type )?
ConstParam →
const IDENTIFIER : Type
( = ( BlockExpression | IDENTIFIER | -? LiteralExpression ) )?
함수, 타입 별칭, 구조체, 열거형, 공용체, 트레잇, 그리고 구현 은 타입, 상수, 라이프타임에 의해 매개변수화 될 수 있습니다. 이러한 매개변수들은 꺽쇠 괄호(<...>) 안에 나열되며, 보통 아이템의 이름 바로 뒤이자 정의 앞에 위치합니다. 이름이 없는 구현의 경우, impl 바로 뒤에 옵니다.
제네릭 매개변수의 순서는 라이프타임 매개변수가 먼저 오고, 그 뒤에 타입 매개변수와 상수 매개변수가 섞여서 오는 것으로 제한됩니다.
The same parameter name may not be declared more than once in a GenericParams list.
타입, 상수, 라이프타임 매개변수를 가진 아이템의 몇 가지 예시:
#![allow(unused)]
fn main() {
fn foo<'a, T>() {}
trait A<U> {}
struct Ref<'a, T> where T: 'a { r: &'a T }
struct InnerArray<T, const N: usize>([T; N]);
struct EitherOrderWorks<const N: bool, U>(U);
}
제네릭 매개변수는 그것이 선언된 아이템 정의 내의 스코프에 있습니다. 아이템 선언에서 설명한 대로, 함수 본문 내에 선언된 아이템들에 대해서는 스코프에 있지 않습니다. 자세한 내용은 제네릭 매개변수 스코프 를 참조하세요.
참조, 원시 포인터, 배열, 슬라이스, 튜플, 그리고 함수 포인터 또한 라이프타임 또는 타입 매개변수를 가지지만, 경로 구문을 통해 참조되지는 않습니다.
'_ 와 'static 은 유효한 라이프타임 매개변수 이름이 아닙니다.
상수 제네릭
상수 제네릭 매개변수 를 통해 아이템이 상수 값에 대해 제네릭해질 수 있습니다.
상수 식별자는 값 네임스페이스 에 상수 매개변수를 위한 이름을 도입하며, 해당 아이템의 모든 인스턴스는 주어진 타입의 값으로 인스턴스화되어야 합니다.
상수 매개변수로 허용되는 타입은 u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, char 그리고 bool 뿐입니다.
상수 매개변수는 상수 아이템 이 사용될 수 있는 모든 곳에서 사용될 수 있습니다. 단, 타입 이나 배열 반복 표현식에서 사용될 때는 (아래에서 설명하듯이) 단독으로(standalone) 사용되어야 합니다. 즉, 다음과 같은 장소에서 허용됩니다.
- 해당 아이템 시그니처의 일부를 형성하는 모든 타입에 적용된 상수로 사용될 때.
- 연관 상수 를 정의하는 데 사용되는 상수 표현식의 일부로 사용되거나, 연관 타입의 매개변수로 사용될 때.
- 해당 아이템 내 모든 함수의 본문에 있는 모든 런타임 표현식의 값으로 사용될 때.
- 해당 아이템 내 모든 함수의 본문에서 사용되는 모든 타입의 매개변수로 사용될 때.
- 해당 아이템 내 모든 필드의 타입의 일부로 사용될 때.
#![allow(unused)]
fn main() {
// 상수 제네릭 매개변수를 사용할 수 있는 예시.
// 아이템 자체의 시그니처에서 사용됨.
fn foo<const N: usize>(arr: [i32; N]) {
// 함수 본문 내에서 타입으로 사용됨.
let x: [i32; N];
// 표현식으로 사용됩니다.
println!("{}", N * 2);
}
// 구조체의 필드로 사용됨.
struct Foo<const N: usize>([i32; N]);
impl<const N: usize> Foo<N> {
// 연관 상수로 사용됨.
const CONST: usize = N * 4;
}
trait Trait {
type Output;
}
impl<const N: usize> Trait for Foo<N> {
// 연관 타입으로 사용됨.
type Output = [i32; N];
}
}
#![allow(unused)]
fn main() {
// 상수 제네릭 매개변수를 사용할 수 없는 예시.
fn foo<const N: usize>() {
// 함수 본문 내의 아이템 정의에서는 사용할 수 없음.
const BAD_CONST: [usize; N] = [1; N];
static BAD_STATIC: [usize; N] = [1; N];
fn inner(bad_arg: [usize; N]) {
let bad_value = N * 2;
}
type BadAlias = [usize; N];
struct BadStruct([usize; N]);
}
}
추가적인 제약 사항으로, 상수 매개변수는 타입 이나 배열 반복 표현식 내부에서 오직 단독(standalone) 인수로만 나타날 수 있습니다. 이러한 컨텍스트에서 상수 매개변수는 오직 단일 세그먼트 경로 표현식 으로만 사용될 수 있으며, 필요한 경우 블록 내부(예: N 또는 {N})에 있을 수 있습니다. 즉, 다른 표현식과 결합될 수 없습니다.
#![allow(unused)]
fn main() {
// 상수 매개변수를 사용할 수 없는 예시.
// 타입 내에서 다른 표현식과 결합하는 것은 허용되지 않습니다. 예를 들어
// 여기 반환 타입에 있는 산술 표현식과 같은 경우입니다.
fn bad_function<const N: usize>() -> [u8; {N + 1}] {
// 배열 반복 표현식에서도 마찬가지로 허용되지 않습니다.
[1; {N + 1}]
}
}
경로 에 있는 상수 인수는 해당 아이템에 사용할 상수 값을 지정합니다.
The argument must either be an inferred const or be a const expression of the type ascribed to the const parameter. The const expression must be a block expression (surrounded with braces) unless it is a single path segment (an IDENTIFIER) or a literal (with a possibly leading - token).
Note
This syntactic restriction is necessary to avoid requiring infinite lookahead when parsing an expression inside of a type.
#![allow(unused)]
fn main() {
struct S<const N: i64>;
const C: i64 = 1;
fn f<const N: i64>() -> S<N> { S }
let _ = f::<1>(); // Literal.
let _ = f::<-1>(); // Negative literal.
let _ = f::<{ 1 + 2 }>(); // Constant expression.
let _ = f::<C>(); // Single segment path.
let _ = f::<{ C + 1 }>(); // Constant expression.
let _: S<1> = f::<_>(); // Inferred const.
let _: S<1> = f::<(((_)))>(); // Inferred const.
}
Note
In a generic argument list, an inferred const is parsed as an inferred type but then semantically treated as a separate kind of const generic argument.
Where a const argument is expected, an _ (optionally surrounded by any number of matching parentheses), called the inferred const (path rules, array expression rules), can be used instead. This asks the compiler to infer the const argument if possible based on surrounding information.
#![allow(unused)]
fn main() {
fn make_buf<const N: usize>() -> [u8; N] {
[0; _]
// ^ Infers `N`.
}
let _: [u8; 1024] = make_buf::<_>();
// ^ Infers `1024`.
}
Note
An inferred const is not semantically an expression and so is not accepted within braces.
#![allow(unused)] fn main() { fn f<const N: usize>() -> [u8; N] { [0; _] } let _: [_; 1] = f::<{ _ }>(); // ^ ERROR `_` not allowed here }
The inferred const cannot be used in item signatures.
#![allow(unused)]
fn main() {
fn f<const N: usize>(x: [u8; N]) -> [u8; _] { x }
// ^ ERROR not allowed
}
제네릭 인수가 타입 인수와 상수 인수 중 어느 것으로도 해석될 수 있어 모호한 경우, 항상 타입으로 해석됩니다. 인수를 블록 표현식 안에 배치하면 강제로 상수 인수로 해석되게 할 수 있습니다.
#![allow(unused)]
fn main() {
type N = u32;
struct Foo<const N: usize>;
// 다음은 오류입니다. `N` 이 타입 별칭 `N` 으로 해석되기 때문입니다.
fn foo<const N: usize>() -> Foo<N> { todo!() } // 오류
// 중괄호로 감싸서 강제로 상수 매개변수 `N` 으로 해석되게 함으로써
// 해결할 수 있습니다.
fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ok
}
타입 및 라이프타임 매개변수와 달리, 상수 매개변수는 제네릭 구현에서 설명한 구현의 경우를 제외하고는 매개변수화된 아이템 내부에서 사용되지 않고도 선언될 수 있습니다.
#![allow(unused)]
fn main() {
// ok
struct Foo<const N: usize>;
enum Bar<const M: usize> { A, B }
// 오류: 사용되지 않은 매개변수
struct Baz<T>;
struct Biz<'a>;
struct Unconstrained;
impl<const N: usize> Unconstrained {}
}
트레잇 바운드 의무를 해결할 때, 바운드가 충족되는지 여부를 판단하기 위해 상수 매개변수의 모든 구현에 대한 완전성(exhaustiveness)은 고려되지 않습니다. 예를 들어, 다음 코드에서는 bool 타입에 대해 가능한 모든 상수 값들이 구현되었음에도 불구하고, 여전히 트레잇 바운드가 충족되지 않았다는 오류가 발생합니다.
#![allow(unused)]
fn main() {
struct Foo<const B: bool>;
trait Bar {}
impl Bar for Foo<true> {}
impl Bar for Foo<false> {}
fn needs_bar(_: impl Bar) {}
fn generic<const B: bool>() {
let v = Foo::<B>;
needs_bar(v); // 오류: 트레잇 바운드 `Foo<B>: Bar` 가 충족되지 않음
}
}
Where 절
Syntax
WhereClause → where ( WhereClauseItem , )* WhereClauseItem?
WhereClauseItem →
LifetimeWhereClauseItem
| TypeBoundWhereClauseItem
LifetimeWhereClauseItem → Lifetime : LifetimeBounds
TypeBoundWhereClauseItem → ForLifetimes? Type : TypeParamBounds?
Where 절 은 타입 및 라이프타임 매개변수에 대한 바운드를 지정하는 또 다른 방법이자, 타입 매개변수가 아닌 타입에 대해 바운드를 지정하는 방법을 제공합니다.
The for keyword can be used to introduce higher-ranked lifetimes. It only allows LifetimeParam parameters.
#![allow(unused)]
fn main() {
struct A<T>
where
T: Iterator, // 대신 A<T: Iterator>를 사용할 수 있습니다.
T::Item: Copy, // 연관 타입에 대한 바운드
String: PartialEq<T>, // 타입 매개변수를 사용하여 `String` 에 대한 바운드 지정
i32: Default, // 허용되지만 유용하지는 않음
{
f: T,
}
}
속성
제네릭 라이프타임 및 타입 매개변수에는 속성 을 붙일 수 있습니다. 이 위치에서 무언가를 수행하는 내장 속성은 없지만, 커스텀 derive 속성이 여기에 의미를 부여할 수 있습니다.
이 예시는 커스텀 derive 속성을 사용하여 제네릭 매개변수의 의미를 수정하는 방법을 보여줍니다.
// MyFlexibleClone에 대한 derive가 `my_flexible_clone` 을 이해할 수 있는
// 속성으로 선언했다고 가정합니다.
#[derive(MyFlexibleClone)]
struct Foo<#[my_flexible_clone(unbounded)] H> {
a: *const H
}
Associated items
Syntax
AssociatedItem →
OuterAttribute* (
MacroInvocationSemi
| ( Visibility? ( TypeAlias | ConstantItem | Function ) )
)
연관 아이템(Associated Items) 은 트레잇 에 선언되거나 구현 에 정의된 아이템들을 말합니다. 연관 아이템이라고 불리는 이유는 그것들이 연관된 타입 — 즉 구현 내의 타입 — 에 정의되기 때문입니다.
이들은 모듈에서 선언할 수 있는 아이템 종류의 하위 집합입니다. 구체적으로 연관 함수(메서드 포함), 연관 타입, 그리고 연관 상수 가 있습니다.
Associated items are useful when the associated item is logically related to the associating item. For example, the is_some method on Option is intrinsically related to Options, so should be associated.
모든 종류의 연관 아이템은 두 가지 변형으로 제공됩니다: 실제 구현을 포함하는 정의(definitions)와 정의를 위한 시그니처를 선언하는 선언(declarations)입니다.
트레잇의 계약을 구성하고 제네릭 타입에서 무엇을 사용할 수 있는지를 결정하는 것은 바로 선언입니다.
연관 함수와 메서드
연관 함수 는 타입과 연관된 함수 입니다.
연관 함수 선언 은 연관 함수 정의를 위한 시그니처를 선언합니다. 함수 본문이 ; 로 대체된다는 점을 제외하면 함수 아이템과 동일하게 작성됩니다.
식별자는 함수의 이름입니다.
연관 함수의 제네릭, 매개변수 목록, 반환 타입, 그리고 Where 절은 연관 함수 선언과 동일해야 합니다.
연관 함수 정의 는 다른 타입과 연관된 함수를 정의합니다. 함수 아이템과 동일하게 작성됩니다.
Note
A common example is an associated function named
newthat returns a value of the type with which it is associated.
struct Struct {
field: i32
}
impl Struct {
fn new() -> Struct {
Struct {
field: 0i32
}
}
}
fn main () {
let _struct = Struct::new();
}
연관 함수가 트레잇에 선언된 경우, 해당 함수는 트레잇으로의 경로 뒤에 트레잇 이름이 붙은 경로 로도 호출될 수 있습니다. 이 경우, <_ as Trait>::function_name으로 대체됩니다.
#![allow(unused)]
fn main() {
trait Num {
fn from_i32(n: i32) -> Self;
}
impl Num for f64 {
fn from_i32(n: i32) -> f64 { n as f64 }
}
// 이 경우 이 4가지는 모두 동일합니다.
let _: f64 = Num::from_i32(42);
let _: f64 = <_ as Num>::from_i32(42);
let _: f64 = <f64 as Num>::from_i32(42);
let _: f64 = f64::from_i32(42);
}
메서드
첫 번째 매개변수의 이름이 self 인 연관 함수를 메서드 라고 하며, 메서드 호출 연산자(예: x.foo())를 사용하거나 일반적인 함수 호출 표기법을 사용하여 호출할 수 있습니다.
self 매개변수의 타입이 지정된 경우, 다음 문법에 의해 생성된 타입 중 하나로 해석되는 타입으로 제한됩니다(여기서 'lt 는 임의의 라이프타임을 나타냄):
P = &'lt S | &'lt mut S | Box<S> | Rc<S> | Arc<S> | Pin<P>
S = Self | P
이 문법에서 Self 터미널(terminal)은 구현 타입으로 해석되는 타입을 나타냅니다. 여기에는 문맥적 타입 별칭 Self, 다른 타입 별칭, 또는 구현 타입으로 해석되는 연관 타입 투영(associated type projections)이 포함될 수 있습니다.
#![allow(unused)]
fn main() {
use std::rc::Rc;
use std::sync::Arc;
use std::pin::Pin;
// `Example` 구조체에 구현된 메서드의 예시.
struct Example;
type Alias = Example;
trait Trait { type Output; }
impl Trait for Example { type Output = Example; }
impl Example {
fn by_value(self: Self) {}
fn by_ref(self: &Self) {}
fn by_ref_mut(self: &mut Self) {}
fn by_box(self: Box<Self>) {}
fn by_rc(self: Rc<Self>) {}
fn by_arc(self: Arc<Self>) {}
fn by_pin(self: Pin<&Self>) {}
fn explicit_type(self: Arc<Example>) {}
fn with_lifetime<'a>(self: &'a Self) {}
fn nested<'a>(self: &mut &'a Arc<Rc<Box<Alias>>>) {}
fn via_projection(self: <Example as Trait>::Output) {}
}
}
타입을 지정하지 않고 단축 구문을 사용할 수 있으며, 이는 다음과 동일합니다:
| 단축 구문 | 동등한 표현 |
|---|---|
self | self: Self |
&'lifetime self | self: &'lifetime Self |
&'lifetime mut self | self: &'lifetime mut Self |
Note
Lifetimes can be, and usually are, elided with this shorthand.
self 매개변수 앞에 mut 가 붙으면, mut 식별자 패턴 을 사용하는 일반 매개변수와 마찬가지로 가변 변수가 됩니다. 예를 들어:
#![allow(unused)]
fn main() {
trait Changer: Sized {
fn change(mut self) {}
fn modify(mut self: Box<Self>) {}
}
}
트레잇의 메서드 예시는 다음과 같습니다:
#![allow(unused)]
fn main() {
type Surface = i32;
type BoundingBox = i32;
trait Shape {
fn draw(&self, surface: Surface);
fn bounding_box(&self) -> BoundingBox;
}
}
이것은 두 개의 메서드를 가진 트레잇을 정의합니다. 트레잇이 스코프 내에 있는 동안 이 트레잇의 구현 을 가진 모든 값은 draw 및 bounding_box 메서드를 호출할 수 있습니다.
#![allow(unused)]
fn main() {
type Surface = i32;
type BoundingBox = i32;
trait Shape {
fn draw(&self, surface: Surface);
fn bounding_box(&self) -> BoundingBox;
}
struct Circle {
// ...
}
impl Shape for Circle {
// ...
fn draw(&self, _: Surface) {}
fn bounding_box(&self) -> BoundingBox { 0i32 }
}
impl Circle {
fn new() -> Circle { Circle{} }
}
let circle_shape = Circle::new();
let bounding_box = circle_shape.bounding_box();
}
2018 Edition differences
In the 2015 edition, it is possible to declare trait methods with anonymous parameters (e.g.
fn foo(u8)). This is deprecated and an error as of the 2018 edition. All parameters must have an argument name.
메서드 매개변수의 속성
메서드 매개변수의 속성은 일반 함수 매개변수와 동일한 규칙 및 제약 사항을 따릅니다.
Associated types
연관 타입 은 다른 타입과 연관된 타입 별칭 입니다.
연관 타입은 고유 구현 에서 정의될 수 없으며 트레잇에서 기본 구현을 가질 수도 없습니다.
연관 타입 선언 은 연관 타입 정의를 위한 시그니처를 선언합니다. 다음 형식 중 하나로 작성되며, Assoc 은 연관 타입의 이름, Params 는 쉼표로 구분된 타입, 라이프타임 또는 상수 매개변수 목록, Bounds 는 연관 타입이 충족해야 하는 더하기로 구분된 트레잇 바운드 목록, WhereBounds 는 매개변수가 충족해야 하는 쉼표로 구분된 바운드 목록입니다:
type Assoc;
type Assoc: Bounds;
type Assoc<Params>;
type Assoc<Params>: Bounds;
type Assoc<Params> where WhereBounds;
type Assoc<Params>: Bounds where WhereBounds;
식별자는 선언된 타입 별칭의 이름입니다.
선택적 트레잇 바운드는 타입 별칭의 구현에 의해 충족되어야 합니다.
연관 타입에는 암시적인 Sized 바운드가 있으며, 이는 특수한 ?Sized 바운드를 사용하여 완화할 수 있습니다.
An associated type definition defines a type alias for the implementation of a trait on a type.
이들은 연관 타입 선언 과 유사하게 작성되지만, Bounds 를 포함할 수 없고 대신 Type 을 포함해야 합니다:
type Assoc = Type;
type Assoc<Params> = Type; // 여기서 `Type` 타입은 `Params` 를 참조할 수 있습니다
type Assoc<Params> = Type where WhereBounds;
type Assoc<Params> where WhereBounds = Type; // 사용 중단됨(deprecated), 위의 형식을 선호하세요
If a type Item has an associated type Assoc from a trait Trait, then <Item as Trait>::Assoc is a type that is an alias of the type specified in the associated type definition.
또한, Item 이 타입 매개변수인 경우, Item::Assoc 은 타입 매개변수 내에서 사용될 수 있습니다.
연관 타입은 제네릭 매개변수 와 where 절 을 포함할 수 있습니다. 이들은 종종 제네릭 연관 타입(generic associated types) 또는 GAT 라고 불립니다. 타입 Thing 이 제네릭 <'a> 를 가진 트레잇 Trait 으로부터 연관 타입 Item 을 가진다면, 이 타입은 <Thing as Trait>::Item<'x> 와 같이 명명될 수 있으며, 여기서 'x 는 스코프 내의 어떤 라이프타임입니다. 이 경우, 구현(impl)의 연관 타입 정의에서 'a 가 나타나는 모든 곳에 'x 가 사용됩니다.
trait AssociatedType {
// 연관 타입 선언
type Assoc;
}
struct Struct;
struct OtherStruct;
impl AssociatedType for Struct {
// 연관 타입 정의
type Assoc = OtherStruct;
}
impl OtherStruct {
fn new() -> OtherStruct {
OtherStruct
}
}
fn main() {
// <Struct as AssociatedType>::Assoc와 같이 OtherStruct를 참조하기 위해 연관 타입을 사용함
let _other_struct: OtherStruct = <Struct as AssociatedType>::Assoc::new();
}
제네릭과 where 절을 사용한 연관 타입의 예시:
struct ArrayLender<'a, T>(&'a mut [T; 16]);
trait Lend {
// 제네릭 연관 타입 선언
type Lender<'a> where Self: 'a;
fn lend<'a>(&'a mut self) -> Self::Lender<'a>;
}
impl<T> Lend for [T; 16] {
// 제네릭 연관 타입 정의
type Lender<'a> = ArrayLender<'a, T> where Self: 'a;
fn lend<'a>(&'a mut self) -> Self::Lender<'a> {
ArrayLender(self)
}
}
fn borrow<'a, T: Lend>(array: &'a mut T) -> <T as Lend>::Lender<'a> {
array.lend()
}
fn main() {
let mut array = [0usize; 16];
let lender = borrow(&mut array);
}
Associated types container example
다음 Container 트레잇 예시를 고려해 보세요. 해당 타입이 메서드 시그니처에서 사용 가능하다는 점에 주목하세요:
#![allow(unused)]
fn main() {
trait Container {
type E;
fn empty() -> Self;
fn insert(&mut self, elem: Self::E);
}
}
어떤 타입이 이 트레잇을 구현하기 위해서는, 모든 메서드에 대한 구현을 제공해야 할 뿐만 아니라 타입 E 를 명시해야 합니다. 다음은 표준 라이브러리 타입 Vec 에 대한 Container 구현입니다:
#![allow(unused)]
fn main() {
trait Container {
type E;
fn empty() -> Self;
fn insert(&mut self, elem: Self::E);
}
impl<T> Container for Vec<T> {
type E = T;
fn empty() -> Vec<T> { Vec::new() }
fn insert(&mut self, x: T) { self.push(x); }
}
}
Bounds 와 WhereBounds 사이의 관계
이 예시에서:
#![allow(unused)]
fn main() {
use std::fmt::Debug;
trait Example {
type Output<T>: Ord where T: Debug;
}
}
<X as Example>::Output<Y> 와 같은 연관 타입에 대한 참조가 주어지면, 연관 타입 자체는 반드시 Ord 여야 하고, 타입 Y 는 반드시 Debug 여야 합니다.
제네릭 연관 타입에 요구되는 where 절
트레잇의 제네릭 연관 타입 선언은 현재 트레잇의 함수들과 GAT가 어떻게 사용되는지에 따라 where 절 목록을 요구할 수 있습니다. 이러한 규칙들은 미래에 완화될 수 있습니다. 업데이트는 제네릭 연관 타입 이니셔티브 저장소 에서 확인할 수 있습니다.
간단히 말해서, 이러한 where 절은 구현(impl)에서 연관 타입의 허용되는 정의를 최대화하기 위해 필요합니다. 이를 위해, GAT가 입력 또는 출력으로 나타나는 함수에서 (함수나 트레잇의 매개변수를 사용하여) 성립한다고 증명될 수 있는 모든 절은 GAT 자체에도 작성되어야 합니다.
#![allow(unused)]
fn main() {
trait LendingIterator {
type Item<'x> where Self: 'x;
fn next<'a>(&'a mut self) -> Self::Item<'a>;
}
}
위의 next 함수에서, &'a mut self 로부터 암시된 바운드 덕분에 Self: 'a 임을 증명할 수 있습니다. 따라서 GAT 자체에 동일한 바운드인 where Self: 'x 를 작성해야 합니다.
트레잇에 GAT를 사용하는 함수가 여러 개 있는 경우, 합집합이 아닌 각 함수의 바운드들의 교집합 이 사용됩니다.
#![allow(unused)]
fn main() {
trait Check<T> {
type Checker<'x>;
fn create_checker<'a>(item: &'a T) -> Self::Checker<'a>;
fn do_check(checker: Self::Checker<'_>);
}
}
이 예시에서는 type Checker<'a>; 에 아무런 바운드가 요구되지 않습니다. create_checker 에서는 T: 'a 임을 알 수 있지만, do_check 에서는 이를 알 수 없기 때문입니다. 하지만 만약 do_check 가 주석 처리된다면, Checker 에 where T: 'x 바운드가 요구될 것입니다.
연관 타입에 대한 바운드는 요구되는 where 절을 전파하기도 합니다.
#![allow(unused)]
fn main() {
trait Iterable {
type Item<'a> where Self: 'a;
type Iterator<'a>: Iterator<Item = Self::Item<'a>> where Self: 'a;
fn iter<'a>(&'a self) -> Self::Iterator<'a>;
}
}
여기서 iter 때문에 Item 에 where Self: 'a 가 요구됩니다. 그런데 Item 이 Iterator 의 바운드에서 사용되므로, where Self: 'a 절이 거기서도 요구됩니다.
마지막으로, 트레잇의 GAT에서 'static 을 명시적으로 사용하는 것은 요구되는 바운드에 포함되지 않습니다.
#![allow(unused)]
fn main() {
trait StaticReturn {
type Y<'a>;
fn foo(&self) -> Self::Y<'static>;
}
}
Associated constants
연관 상수 는 타입과 연관된 상수 입니다.
연관 상수 선언 은 연관 상수 정의를 위한 시그니처를 선언합니다. const, 식별자, :, 타입 순으로 작성하고 ; 로 마칩니다.
식별자는 경로에서 사용되는 상수의 이름입니다. 타입은 정의에서 구현해야 하는 타입입니다.
연관 상수 정의 는 타입과 연관된 상수를 정의합니다. 상수 아이템 과 동일하게 작성됩니다.
연관 상수 정의는 참조될 때만 상수 평가 를 거칩니다. 또한, 제네릭 매개변수 를 포함하는 정의는 모노모르포화(monomorphization) 후에 평가됩니다.
struct Struct;
struct GenericStruct<const ID: i32>;
impl Struct {
// 정의가 즉시 평가되지 않음
const PANIC: () = panic!("컴파일 타임 패닉");
}
impl<const ID: i32> GenericStruct<ID> {
// 정의가 즉시 평가되지 않음
const NON_ZERO: () = if ID == 0 {
panic!("모순")
};
}
fn main() {
// Struct::PANIC을 참조하면 컴파일 오류가 발생함
let _ = Struct::PANIC;
// 괜찮음, ID가 0이 아님
let _ = GenericStruct::<1>::NON_ZERO;
// ID=0으로 NON_ZERO를 평가할 때 발생하는 컴파일 오류
let _ = GenericStruct::<0>::NON_ZERO;
}
Associated constants examples
기본 예시:
trait ConstantId {
const ID: i32;
}
struct Struct;
impl ConstantId for Struct {
const ID: i32 = 1;
}
fn main() {
assert_eq!(1, Struct::ID);
}
기본값 사용하기:
trait ConstantIdDefault {
const ID: i32 = 1;
}
struct Struct;
struct OtherStruct;
impl ConstantIdDefault for Struct {}
impl ConstantIdDefault for OtherStruct {
const ID: i32 = 5;
}
fn main() {
assert_eq!(1, Struct::ID);
assert_eq!(5, OtherStruct::ID);
}
속성
Syntax
InnerAttribute → # ! [ Attr ]
OuterAttribute → # [ Attr ]
Attr →
SimplePath AttrInput?
| unsafe ( SimplePath AttrInput? )
속성(attribute) 은 이름, 관례, 언어, 컴파일러 버전에 따라 해석되는 일반적이고 자유로운 형식의 메타데이터입니다. 속성은 ECMA-335 의 속성을 모델로 하며, 구문은 ECMA-334(C#)에서 유래했습니다.
Inner attributes, written with a bang (!) after the hash (#), apply to the form that the attribute is declared within.
Example
#![allow(unused)] fn main() { // 둘러싼 모듈이나 크레이트에 적용되는 일반 메타데이터입니다. #![crate_type = "lib"] // 내부 속성은 함수 전체에 적용됩니다. fn some_unused_variables() { #![allow(unused_variables)] let x = (); let y = (); let z = (); } }
Outer attributes, written without the bang after the hash, apply to the form that follows the attribute.
Example
#![allow(unused)] fn main() { // 유닛 테스트로 표시된 함수입니다. #[test] fn test_foo() { /* ... */ } // 조건부 컴파일되는 모듈입니다. #[cfg(target_os = "linux")] mod bar { /* ... */ } // 경고/오류를 억제하기 위해 사용되는 린트(lint) 속성입니다. #[allow(non_camel_case_types)] type int8_t = i8; }
속성은 속성으로의 경로와, 속성에 의해 해석이 정의되는 선택적인 구분된 토큰 트리로 구성됩니다. 매크로 속성을 제외한 속성들은 등호(=) 뒤에 표현식이 오는 입력도 허용합니다. 자세한 내용은 아래의 메타 아이템 구문 을 참조하세요.
An attribute may be unsafe to apply. To avoid undefined behavior when using these attributes, certain obligations that cannot be checked by the compiler must be met. To assert these have been, the attribute is wrapped in unsafe(..), e.g. #[unsafe(no_mangle)].
다음 속성들은 안전하지 않습니다:
속성은 다음과 같은 종류로 분류될 수 있습니다:
Attributes may be applied to many forms in the language:
- 모든 아이템 선언 은 외부 속성을 허용하며, 외부 블록, 함수, 구현, 모듈 은 내부 속성을 허용합니다.
- 대부분의 구문 은 외부 속성을 허용합니다 (표현식 구문에 대한 제약 사항은 표현식 속성 을 참조하세요).
- 블록 표현식 은 외부 및 내부 속성을 허용하지만, 표현식 구문 의 외부 표현식이거나 다른 블록 표현식의 마지막 표현식인 경우에만 해당합니다.
- 열거형 변형과 구조체 및 공용체 필드는 외부 속성을 허용합니다.
- 매치 표현식 암(Match expression arms) 은 외부 속성을 허용합니다.
- 제네릭 라이프타임 또는 타입 매개변수 는 외부 속성을 허용합니다.
- 표현식은 제한적인 상황에서 외부 속성을 허용합니다. 자세한 내용은 표현식 속성 을 참조하세요.
- 함수, 클로저 및 함수 포인터 매개변수는 외부 속성을 허용합니다. 여기에는 함수 포인터와 외부 블록 에서
...로 표시된 가변 매개변수에 대한 속성도 포함됩니다. - Inline assembly template strings and operands accept outer attributes. Only certain attributes are accepted semantically; for details, see asm.attributes.supported-attributes.
Meta item attribute syntax
A “meta item” is the syntax used for the Attr rule by most built-in attributes. It has the following grammar:
Syntax
MetaItem →
SimplePath
| SimplePath = Expression
| SimplePath ( MetaSeq? )
MetaSeq →
MetaItemInner ( , MetaItemInner )* ,?
메타 아이템 내의 표현식은 반드시 리터럴 표현식으로 매크로 확장되어야 하며, 여기에는 정수 또는 부동 소수점 타입 접미사가 포함되어서는 안 됩니다. 리터럴 표현식이 아닌 표현식은 구문적으로는 허용되지만(절차적 매크로로 전달될 수 있음), 파싱 후에는 거부됩니다.
속성이 다른 매크로 내에 나타나는 경우, 해당 외부 매크로 다음에 확장된다는 점에 유의하세요. 예를 들어, 다음 코드는 Serialize 절차적 매크로를 먼저 확장하며, include_str! 호출이 확장되기 위해서는 이 매크로가 해당 호출을 보존해야 합니다.
#[derive(Serialize)]
struct Foo {
#[doc = include_str!("x.md")]
x: u32
}
추가적으로, 속성 내의 매크로는 아이템에 적용된 다른 모든 속성들이 확장된 후에만 확장됩니다.
#[macro_attr1] // 첫 번째로 확장됨
#[doc = mac!()] // `mac!` 은 네 번째로 확장됨
#[macro_attr2] // 두 번째로 확장됨
#[derive(MacroDerive1, MacroDerive2)] // 세 번째로 확장됨
fn foo() {}
여러 내장 속성들은 입력을 지정하기 위해 메타 아이템 구문의 서로 다른 하위 집합을 사용합니다. 다음 문법 규칙들은 자주 사용되는 몇 가지 형식을 보여줍니다:
Syntax
MetaWord →
IDENTIFIER
MetaNameValueStr →
IDENTIFIER = ( STRING_LITERAL | RAW_STRING_LITERAL )
MetaListPaths →
IDENTIFIER ( ( SimplePath ( , SimplePath )* ,? )? )
MetaListIdents →
IDENTIFIER ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )
MetaListNameValueStr →
IDENTIFIER ( ( MetaNameValueStr ( , MetaNameValueStr )* ,? )? )
메타 아이템의 몇 가지 예시는 다음과 같습니다:
| 스타일 | 예시 |
|---|---|
| MetaWord | no_std |
| MetaNameValueStr | doc = "example" |
| MetaListPaths | allow(unused, clippy::inline_always) |
| MetaListIdents | macro_use(foo, bar) |
| MetaListNameValueStr | link(name = "CoreFoundation", kind = "framework") |
활성 및 비활성 속성
An attribute is either active or inert. During attribute processing, active attributes remove themselves from the form they are on while inert attributes stay on.
The cfg and cfg_attr attributes are active. Attribute macros are active. All other attributes are inert.
도구 속성
컴파일러는 각 도구가 도구 프렐류드(tool prelude) 의 자체 모듈에 상주하는 외부 도구들을 위한 속성을 허용할 수 있습니다. 속성 경로의 첫 번째 세그먼트는 도구의 이름이며, 하나 이상의 추가 세그먼트의 해석은 도구에 달려 있습니다.
도구가 사용되지 않을 때, 해당 도구의 속성은 경고 없이 수용됩니다. 도구가 사용 중일 때, 도구는 해당 속성의 처리 및 해석을 담당합니다.
no_implicit_prelude 속성이 사용되는 경우 도구 속성을 사용할 수 없습니다.
#![allow(unused)]
fn main() {
// rustfmt 도구에게 다음 요소를 포맷팅하지 않도록 지시합니다.
#[rustfmt::skip]
struct S {
}
// clippy 도구의 "순환 복잡도(cyclomatic complexity)" 임계값을 제어합니다.
#[clippy::cyclomatic_complexity = "100"]
pub fn f() {}
}
Note
rustccurrently recognizes the tools “clippy”, “rustfmt”, “diagnostic”, “miri”, and “rust_analyzer”.
내장 속성 색인
다음은 모든 내장 속성의 색인입니다.
-
조건부 컴파일
-
테스팅
test— 함수를 테스트로 표시합니다.ignore— 테스트 함수를 비활성화합니다.should_panic— 테스트가 패닉을 발생시켜야 함을 나타냅니다.
-
파생
derive— 트레잇 자동 구현.automatically_derived—derive에 의해 생성된 구현을 위한 마커입니다.
-
매크로
macro_export— Exports amacro_rulesmacro for cross-crate usage.macro_use— 매크로 가시성을 확장하거나 다른 크레이트에서 매크로를 임포트합니다.proc_macro— Defines a function-like macro.proc_macro_derive— Defines a derive macro.proc_macro_attribute— Defines an attribute macro.
-
진단
allow,expect,warn,deny,forbid— 기본 린트(lint) 레벨을 변경합니다.deprecated— 사용 중단(deprecation) 공지를 생성합니다.must_use— 사용되지 않은 값에 대해 린트를 생성합니다.diagnostic::on_unimplemented— 트레잇이 구현되지 않은 경우 특정 에러 메시지를 내보내도록 컴파일러에 힌트를 줍니다.diagnostic::do_not_recommend— 에러 메시지에서 특정 트레잇 구현을 표시하지 않도록 컴파일러에 힌트를 줍니다.
-
ABI, 링크, 심볼 및 FFI
link—extern블록과 링크할 네이티브 라이브러리를 지정합니다.link_name—extern블록 내의 함수나 정적 아이템을 위한 심볼 이름을 지정합니다.link_ordinal—extern블록 내의 함수나 정적 아이템을 위한 심볼의 오디널(ordinal)을 지정합니다.no_link— 외부 크레이트가 링크되는 것을 방지합니다.repr— 타입 레이아웃을 제어합니다.crate_type— 크레이트의 종류(라이브러리, 실행 파일 등)를 지정합니다.no_main—main심볼 내보내기를 비활성화합니다.export_name— 함수나 정적 아이템에 대해 내보낼 심볼 이름을 지정합니다.link_section— 함수나 정적 아이템에 사용할 객체 파일의 섹션을 지정합니다.no_mangle— 심볼 이름 인코딩(mangling)을 비활성화합니다.used— 컴파일러가 출력 객체 파일에서 정적 아이템을 유지하도록 강제합니다.crate_name— 크레이트 이름을 지정합니다.
-
코드 생성
inline— 코드 인라이닝에 대한 힌트입니다.cold— 함수가 거의 호출되지 않을 것임을 나타내는 힌트입니다.naked— Prevent the compiler from emitting a function prologue and epilogue.no_builtins— 특정 내장 함수들의 사용을 비활성화합니다.target_feature— 플랫폼별 코드 생성을 구성합니다.track_caller— 부모 호출 위치를std::panic::Location::caller()로 전달합니다.instruction_set— Specify the instruction set used to generate a function’s code.
-
문서화
-
프렐류드
no_std— 프렐류드에서 std를 제거합니다.no_implicit_prelude— 모듈 내에서 프렐류드 조회를 비활성화합니다.
-
모듈
path— 모듈의 파일 이름을 지정합니다.
-
제한
recursion_limit— 특정 컴파일 타임 연산에 대한 최대 재귀 한도를 설정합니다.type_length_limit— 다형성 타입(polymorphic type)의 최대 크기를 설정합니다.
-
런타임
panic_handler— Sets the function to handle panics.global_allocator— 전역 메모리 할당자를 설정합니다.windows_subsystem— 링크할 Windows 하위 시스템을 지정합니다.
-
기능(Features)
feature— 불안정하거나 실험적인 컴파일러 기능을 활성화하는 데 사용됩니다.rustc에 구현된 기능들에 대해서는 Unstable Book 을 참조하세요.
-
타입 시스템
non_exhaustive— 미래에 타입에 더 많은 필드나 변형이 추가될 것임을 나타냅니다.
-
디버거
debugger_visualizer— 타입에 대한 디버거 출력을 지정하는 파일을 포함시킵니다.collapse_debuginfo— 디버그 정보에서 매크로 호출이 인코딩되는 방식을 제어합니다.
테스팅 속성
다음 속성들 은 테스트를 수행하기 위한 함수를 지정하는 데 사용됩니다. 크레이트를 “test” 모드로 컴파일하면 테스트를 실행하기 위한 테스트 하네스와 함께 테스트 함수들이 빌드됩니다. 테스트 모드를 활성화하면 test 조건부 컴파일 옵션 도 활성화됩니다.
test 속성
The test attribute marks a function to be executed as a test.
Example
#![allow(unused)] fn main() { pub fn add(left: u64, right: u64) -> u64 { left + right } #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); } }
The test attribute uses the MetaWord syntax.
The test attribute may only be applied to free functions that are monomorphic, that take no arguments, and where the return type implements the Termination trait.
Note
Some of types that implement the
Terminationtrait include:
()Result<T, E> where T: Termination, E: Debug
Only the first use of test on a function has effect.
Note
rustclints against any use following the first. This may become an error in the future.
The test attribute is exported from the standard library prelude as std::prelude::v1::test.
이러한 함수들은 테스트 모드일 때만 컴파일됩니다.
Note
The test mode is enabled by passing the
--testargument torustcor usingcargo test.
테스트 하네스는 반환된 값의 report 메서드를 호출하며, 결과 ExitCode 가 성공적인 종료를 나타내는지 여부에 따라 테스트를 통과 또는 실패로 분류합니다. 특히:
()를 반환하는 테스트는 종료되고 패닉이 발생하지 않는 한 통과합니다.Result<(), E>를 반환하는 테스트는Ok(())를 반환하는 한 통과합니다.ExitCode::SUCCESS를 반환하는 테스트는 통과하고,ExitCode::FAILURE를 반환하는 테스트는 실패합니다.- 종료되지 않는 테스트는 통과도 실패도 하지 않습니다.
Example
#![allow(unused)] fn main() { use std::io; fn setup_the_thing() -> io::Result<i32> { Ok(1) } fn do_the_thing(s: &i32) -> io::Result<()> { Ok(()) } #[test] fn test_the_thing() -> io::Result<()> { let state = setup_the_thing()?; // 성공할 것으로 예상됨 do_the_thing(&state)?; // 성공할 것으로 예상됨 Ok(()) } }
ignore 속성
The ignore attribute can be used with the test attribute to tell the test harness to not execute that function as a test.
Example
#![allow(unused)] fn main() { #[test] #[ignore] fn check_thing() { // … } }
Note
The
rustctest harness supports the--include-ignoredflag to force ignored tests to be run.
The ignore attribute uses the MetaWord and MetaNameValueStr syntaxes.
The MetaNameValueStr form of the ignore attribute provides a way to specify a reason why the test is ignored.
Example
#![allow(unused)] fn main() { #[test] #[ignore = "아직 구현되지 않음"] fn mytest() { // … } }
The ignore attribute may only be applied to functions annotated with the test attribute.
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
Only the first use of ignore on a function has effect.
Note
rustclints against any use following the first. This may become an error in the future.
Ignored tests are still compiled when in test mode, but they are not executed.
should_panic 속성
The should_panic attribute causes a test to pass only if the test function to which the attribute is applied panics.
Example
#![allow(unused)] fn main() { #[test] #[should_panic(expected = "값들이 일치하지 않음")] fn mytest() { assert_eq!(1, 2, "값들이 일치하지 않음"); } }
The should_panic attribute has these forms:
-
Example
#![allow(unused)] fn main() { #[test] #[should_panic] fn mytest() { panic!("error: some message, and more"); } } -
MetaNameValueStr — The given string must appear within the panic message for the test to pass.
Example
#![allow(unused)] fn main() { #[test] #[should_panic = "some message"] fn mytest() { panic!("error: some message, and more"); } } -
MetaListNameValueStr — As with the MetaNameValueStr syntax, the given string must appear within the panic message.
Example
#![allow(unused)] fn main() { #[test] #[should_panic(expected = "some message")] fn mytest() { panic!("error: some message, and more"); } }
The should_panic attribute may only be applied to functions annotated with the test attribute.
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
Only the first use of should_panic on a function has effect.
Note
rustclints against any use following the first with a future-compatibility warning. This may become an error in the future.
When the MetaNameValueStr form or the MetaListNameValueStr form with the expected key is used, the given string must appear somewhere within the panic message for the test to pass.
The return type of the test function must be ().
파생
The derive attribute invokes one or more derive macros, allowing new items to be automatically generated for data structures. You can create derive macros with procedural macros.
Example
The
PartialEqderive macro emits an implementation ofPartialEqforFoo<T> where T: PartialEq. TheClonederive macro does likewise forClone.#![allow(unused)] fn main() { #[derive(PartialEq, Clone)] struct Foo<T> { a: i32, b: T, } }The generated
implitems are equivalent to:#![allow(unused)] fn main() { struct Foo<T> { a: i32, b: T } impl<T: PartialEq> PartialEq for Foo<T> { fn eq(&self, other: &Foo<T>) -> bool { self.a == other.a && self.b == other.b } } impl<T: Clone> Clone for Foo<T> { fn clone(&self) -> Self { Foo { a: self.a.clone(), b: self.b.clone() } } } }
The derive attribute uses the MetaListPaths syntax to specify a list of paths to derive macros to invoke.
The derive attribute may only be applied to structs, enums, and unions.
The derive attribute may be used any number of times on an item. All derive macros listed in all attributes are invoked.
The derive attribute is exported in the standard library prelude as core::prelude::v1::derive.
Built-in derives are defined in the language prelude. The list of built-in derives are:
The built-in derives include the automatically_derived attribute on the implementations they generate.
During macro expansion, for each element in the list of derives, the corresponding derive macro expands to zero or more items.
automatically_derived 속성
The automatically_derived attribute is used to annotate an implementation to indicate that it was automatically created by a derive macro. It has no direct effect, but it may be used by tools and diagnostic lints to detect these automatically generated implementations.
Example
Given
#[derive(Clone)]onstruct Example, the derive macro may produce:#![allow(unused)] fn main() { struct Example; #[automatically_derived] impl ::core::clone::Clone for Example { #[inline] fn clone(&self) -> Self { Example } } }
The automatically_derived attribute uses the MetaWord syntax.
The automatically_derived attribute may only be applied to an implementation.
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
Using automatically_derived more than once on an implementation has the same effect as using it once.
Note
rustclints against any use following the first.
The automatically_derived attribute has no behavior.
진단 속성
다음 속성들 은 컴파일 중에 진단 메시지를 제어하거나 생성하는 데 사용됩니다.
린트(Lint) 체크 속성
린트 체크는 도달할 수 없는 코드나 누락된 문서와 같이 잠재적으로 바람직하지 않은 코딩 패턴을 지적합니다.
The lint attributes allow, expect, warn, deny, and forbid use the MetaListPaths syntax to specify a list of lint names to change the lint level for the entity to which the attribute applies.
임의의 린트 체크 C 에 대해:
#[allow(C)]는C에 대한 체크를 무시하여 위반 사항이 보고되지 않도록 합니다.
#[expect(C)]는 린트C가 발생할 것으로 예상됨을 나타냅니다. 이 속성은C의 발생을 억제하거나, 예상이 충족되지 않은 경우 경고를 발생시킵니다.
#[warn(C)]는C위반에 대해 경고하지만 컴파일을 계속합니다.
#[deny(C)]는C위반을 발견하면 에러를 발생시킵니다.
#[forbid(C)]는deny(C)와 동일하지만, 이후에 린트 레벨을 변경하는 것도 금지합니다.
Note
The lint checks supported by
rustccan be found viarustc -W help, along with their default settings and are documented in the rustc book.
#![allow(unused)]
fn main() {
pub mod m1 {
// 여기서 누락된 문서는 무시됩니다.
#[allow(missing_docs)]
pub fn undocumented_one() -> i32 { 1 }
// 여기서 누락된 문서는 경고를 발생시킵니다.
#[warn(missing_docs)]
pub fn undocumented_too() -> i32 { 2 }
// 여기서 누락된 문서는 에러를 발생시킵니다.
#[deny(missing_docs)]
pub fn undocumented_end() -> i32 { 3 }
}
}
린트 속성은 이전 속성에서 지정된 레벨을 오버라이드할 수 있습니다. 단, 금지된(forbidden) 린트 레벨을 변경하려고 시도해서는 안 됩니다 (단, deny 는 forbid 컨텍스트 내부에서 허용되지만 무시됩니다). 이전 속성이란 구문 트리에서 상위 레벨에 있는 속성이나, 소스 코드 순서상 왼쪽에서 오른쪽으로 나열된 동일한 엔티티의 이전 속성을 의미합니다.
이 예시는 allow 와 warn 을 사용하여 특정 체크를 켜고 끄는 방법을 보여줍니다.
#![allow(unused)]
fn main() {
#[warn(missing_docs)]
pub mod m2 {
#[allow(missing_docs)]
pub mod nested {
// 여기서 누락된 문서는 무시됩니다.
pub fn undocumented_one() -> i32 { 1 }
// 위의 allow에도 불구하고,
// 여기서 누락된 문서는 경고를 발생시킵니다.
#[warn(missing_docs)]
pub fn undocumented_two() -> i32 { 2 }
}
// 여기서 누락된 문서는 경고를 발생시킵니다.
pub fn undocumented_too() -> i32 { 3 }
}
}
이 예시는 특정 린트 체크에 대해 allow 나 expect 를 사용하는 것을 금지하기 위해 forbid 를 사용하는 방법을 보여줍니다.
#![allow(unused)]
fn main() {
#[forbid(missing_docs)]
pub mod m3 {
// 경고를 토글하려고 시도하면 여기서 에러가 발생합니다.
#[allow(missing_docs)]
/// 2를 반환합니다.
pub fn undocumented_too() -> i32 { 2 }
}
}
Note
rustcallows setting lint levels on the command-line, and also supports setting caps on the lints that are reported.
Lint reasons
모든 린트 속성은 특정 속성이 추가된 이유에 대한 문맥을 제공하기 위해 추가적인 reason 매개변수를 지원합니다. 이 사유는 린트가 정의된 레벨에서 내보내질 때 린트 메시지의 일부로 표시됩니다.
#![allow(unused)]
fn main() {
// `keyword_idents` 는 기본적으로 허용됩니다. 여기서는 에디션을 업데이트할 때
// 식별자 마이그레이션을 피하기 위해 이를 거부(deny)합니다.
#![deny(
keyword_idents,
reason = "미래 호환성을 위해 이러한 식별자들을 피하고 싶습니다"
)]
// 이 이름은 Rust 2015 에디션에서 허용되었습니다. 우리는 여전히 피하고자 합니다
// 미래 호환성을 확보하고 최종 사용자에게 혼란을 주지 않기 위함입니다.
fn dyn() {}
}
사유와 함께 린트를 허용한 또 다른 예시입니다:
#![allow(unused)]
fn main() {
use std::path::PathBuf;
pub fn get_path() -> PathBuf {
// `allow` 속성의 `reason` 매개변수는 독자를 위한 문서 역할을 합니다.
#[allow(unused_mut, reason = "이것은 일부 플랫폼에서만 수정됩니다")]
let mut file_name = PathBuf::from("git");
#[cfg(target_os = "windows")]
file_name.set_extension("exe");
file_name
}
}
#[expect] 속성
#[expect(C)] 속성은 린트 C 에 대한 린트 기대를 생성합니다. 동일한 위치의 #[warn(C)] 속성이 린트를 발생시키는 경우 기대가 충족됩니다. 린트 C 가 발생하지 않아 기대가 충족되지 않으면, 해당 속성 위치에서 unfulfilled_lint_expectations 린트가 발생합니다.
fn main() {
// 이 `#[expect]` 속성은 `unused_variables` 린트가 다음 문장에서 발생할 것이라는
// 린트 기대를 생성합니다. `question` 변수가 `println!` 매크로에 의해
// 사용되므로 이 기대는 충족되지 않습니다. 따라서 해당 속성 위치에서
// `unfulfilled_lint_expectations` 린트가 발생합니다.
#[expect(unused_variables)]
let question = "누가 바다 저 깊은 곳 파인애플에 살까요?";
println!("{question}");
// 이 `#[expect]` 속성은 `answer` 변수가 전혀 사용되지 않으므로 충족될
// 린트 기대를 생성합니다. 평소라면 발생했을 `unused_variables` 린트는
// 억제됩니다. 해당 문장이나 속성에 대해 어떤 경고도 발생하지 않습니다.
#[expect(unused_variables)]
let answer = "네모네모 스폰지밥!";
}
린트 기대는 expect 속성에 의해 억제된 린트 발생에 의해서만 충족됩니다. 만약 allow 나 warn 같은 다른 레벨 속성에 의해 해당 스코프에서 린트 레벨이 수정되면, 린트 발생은 그에 맞춰 처리되며 기대는 충족되지 않은 상태로 남습니다.
#![allow(unused)]
fn main() {
#[expect(unused_variables)]
fn select_song() {
// 이것은 `warn` 속성에 정의된 대로 `unused_variables` 린트를 warn 레벨로
// 내보낼 것입니다. 이는 함수 위에 있는 기대를 충족시키지 못합니다.
#[warn(unused_variables)]
let song_name = "Crab Rave";
// `allow` 속성은 린트 발생을 억제합니다. 이는 `expect` 속성이 아닌
// `allow` 속성에 의해 억제되었으므로 함수 위의 기대를
// 충족시키지 못합니다.
#[allow(unused_variables)]
let song_creator = "Noisestorm";
// 이 `expect` 속성은 변수 위치에서 `unused_variables` 린트 발생을
// 억제할 것입니다. 이 린트 발생은 지역적인 expect 속성에 의해
// 억제되었으므로 함수 위의 `expect` 속성은 여전히 충족되지 않습니다.
#[expect(unused_variables)]
let song_version = "Monstercat Release";
}
}
expect 속성이 여러 린트를 포함하는 경우, 각각은 개별적으로 기대됩니다. 린트 그룹의 경우 그룹 내의 한 린트라도 발생했다면 충분합니다.
#![allow(unused)]
fn main() {
// 이 기대는 함수 내부의 사용되지 않은 값에 의해 충족될 것입니다.
// 발생한 `unused_variables` 린트가 `unused` 린트 그룹에 속하기 때문입니다.
#[expect(unused)]
pub fn thoughts() {
let unused = "예시가 떨어져 가고 있어요";
}
pub fn another_example() {
// 이 속성은 두 개의 린트 기대를 생성합니다. `unused_mut` 린트는
// 억제되며 첫 번째 기대를 충족시킬 것입니다. `unused_variables` 린트는
// 변수가 사용되므로 발생하지 않을 것입니다. 따라서 그 기대는 충족되지
// 않은 상태로 남게 되며, 경고가 발생할 것입니다.
#[expect(unused_mut, unused_variables)]
let mut link = "https://www.rust-lang.org/";
println!("우리 커뮤니티에 오신 것을 환영합니다: {link}");
}
}
Note
The behavior of
#[expect(unfulfilled_lint_expectations)]is currently defined to always generate theunfulfilled_lint_expectationslint.
린트 그룹
린트들은 관련된 린트들의 레벨을 함께 조정할 수 있도록 명명된 그룹으로 조직될 수 있습니다. 명명된 그룹을 사용하는 것은 해당 그룹 내의 린트들을 나열하는 것과 동일합니다.
#![allow(unused)]
fn main() {
// 이는 "unused" 그룹의 모든 린트를 허용합니다.
#[allow(unused)]
// 이는 "unused" 그룹의 "unused_must_use" 린트를
// deny 레벨로 오버라이드합니다.
#[deny(unused_must_use)]
fn example() {
// "unused_variables" 린트가 "unused" 그룹에 속해 있으므로
// 경고를 생성하지 않습니다.
let x = 1;
// 결과가 사용되지 않았고 "unused_must_use"가 "deny"로 설정되었으므로
// 에러를 생성합니다.
std::fs::remove_file("some_file"); // 에러: 반드시 사용되어야 할 `Result` 가 사용되지 않음
}
}
“warnings“라는 이름의 특별한 그룹이 있는데, 이는 “warn” 레벨의 모든 린트를 포함합니다. “warnings” 그룹은 속성 순서를 무시하며, 해당 엔티티 내에서 경고를 발생시킬 모든 린트에 적용됩니다.
#![allow(unused)]
fn main() {
unsafe fn an_unsafe_fn() {}
// 이 두 속성의 순서는 중요하지 않습니다.
#[deny(warnings)]
// unsafe_code 린트는 보통 기본적으로 "allow"입니다.
#[warn(unsafe_code)]
fn example_err() {
// `unsafe_code` 경고가 "deny"로 격상되었으므로 이것은 에러입니다.
unsafe { an_unsafe_fn() } // 에러: `unsafe` 블록 사용
}
}
도구 린트 속성
도구 린트를 사용하면 범위가 지정된(scoped) 린트를 사용하여 특정 도구의 린트를 allow, warn, deny, forbid 할 수 있습니다.
도구 린트는 관련 도구가 활성화된 경우에만 체크됩니다. 만약 allow 와 같은 린트 속성이 존재하지 않는 도구 린트를 참조하더라도, 컴파일러는 해당 도구를 사용하기 전까지는 존재하지 않는 린트에 대해 경고하지 않습니다.
그 외에는 일반 린트 속성과 동일하게 작동합니다.
// clippy의 `pedantic` 린트 그룹 전체를 warn으로 설정합니다.
#![warn(clippy::pedantic)]
// clippy의 `filter_map` 린트 경고를 억제합니다.
#![allow(clippy::filter_map)]
fn main() {
// ...
}
// 이 함수에서만 clippy의 `cmp_nan` 린트를 억제합니다.
#[allow(clippy::cmp_nan)]
fn foo() {
// ...
}
deprecated 속성
deprecated 속성 은 아이템을 사용 중단된 것(deprecated)으로 표시합니다. rustc 는 #[deprecated] 아이템 사용 시 경고를 발생시킵니다. rustdoc 은 사용 가능한 경우 since 버전과 note 를 포함하여 아이템의 사용 중단 여부를 표시합니다.
deprecated 속성은 여러 형식을 가집니다:
deprecated— 일반적인 메시지를 발생시킵니다.deprecated = "message"— 사용 중단 메시지에 주어진 문자열을 포함합니다.- MetaListNameValueStr syntax with two optional fields:
since— 아이템이 사용 중단된 버전 번호를 지정합니다.rustc는 현재 이 문자열을 해석하지 않지만, Clippy 와 같은 외부 도구가 이 값의 유효성을 검사할 수 있습니다.note— 사용 중단 메시지에 포함되어야 할 문자열을 지정합니다. 이는 일반적으로 사용 중단에 대한 설명과 권장되는 대안을 제공하는 데 사용됩니다.
deprecated 속성은 모든 아이템, 트레잇 아이템, 열거형 변형, 구조체 필드, 외부 블록 아이템 또는 매크로 정의 에 적용될 수 있습니다. 트레잇 구현 아이템 에는 적용할 수 없습니다. 모듈 이나 구현 과 같이 다른 아이템을 포함하는 아이템에 적용되면 모든 자식 아이템이 사용 중단 속성을 상속받습니다.
예시는 다음과 같습니다:
#![allow(unused)]
fn main() {
#[deprecated(since = "5.2.0", note = "foo는 거의 사용되지 않았습니다. 사용자들은 대신 bar를 사용해야 합니다")]
pub fn foo() {}
pub fn bar() {}
}
RFC 에 동기와 자세한 내용이 포함되어 있습니다.
must_use 속성
must_use 속성 은 값이 “사용“되지 않았을 때 진단 경고를 발생시키는 데 사용됩니다.
must_use 속성은 사용자 정의 복합 타입(구조체, 열거형, 공용체), 함수, 그리고 트레잇 에 적용될 수 있습니다.
The must_use attribute may include a message by using the MetaNameValueStr syntax such as #[must_use = "example message"]. The message will be given alongside the warning.
사용자 정의 복합 타입에 사용된 경우, 표현식 구문 의 표현식 이 해당 타입을 가지면 unused_must_use 린트를 위반하게 됩니다.
#![allow(unused)]
fn main() {
#[must_use]
struct MustUse {
// 몇몇 필드들
}
impl MustUse {
fn new() -> MustUse { MustUse {} }
}
// `unused_must_use` 린트를 위반합니다.
MustUse::new();
}
함수에 사용된 경우, 표현식 구문 의 표현식 이 해당 함수에 대한 호출 표현식 이면 unused_must_use 린트를 위반하게 됩니다.
#![allow(unused)]
fn main() {
#[must_use]
fn five() -> i32 { 5i32 }
// `unused_must_use` 린트를 위반합니다.
five();
}
트레잇 선언 에 사용된 경우, 해당 트레잇의 impl 트레잇 또는 dyn 트레잇 을 반환하는 함수에 대한 표현식 구문 의 호출 표현식 은 unused_must_use 린트를 위반합니다.
#![allow(unused)]
fn main() {
#[must_use]
trait Critical {}
impl Critical for i32 {}
fn get_critical() -> impl Critical {
4i32
}
// `unused_must_use` 린트를 위반합니다.
get_critical();
}
트레잇 선언 내의 함수에 사용된 경우, 호출 표현식이 해당 트레잇 구현체의 함수인 경우에도 동일하게 적용됩니다.
#![allow(unused)]
fn main() {
trait Trait {
#[must_use]
fn use_me(&self) -> i32;
}
impl Trait for i32 {
fn use_me(&self) -> i32 { 0i32 }
}
// `unused_must_use` 린트를 위반합니다.
5i32.use_me();
}
트레잇 구현 내의 함수에 사용된 경우, 이 속성은 아무런 동작도 하지 않습니다.
Note
Trivial no-op expressions containing the value will not violate the lint. Examples include wrapping the value in a type that does not implement
Dropand then not using that type and being the final expression of a block expression that is not used.#![allow(unused)] fn main() { #[must_use] fn five() -> i32 { 5i32 } // 이들 중 어느 것도 `unused_must_use` 린트를 위반하지 않습니다. (five(),); Some(five()); { five() }; if true { five() } else { 0i32 }; match true { _ => five() }; }
Note
It is idiomatic to use a let statement with a pattern of
_when a must-used value is purposely discarded.#![allow(unused)] fn main() { #[must_use] fn five() -> i32 { 5i32 } // `unused_must_use` 린트를 위반하지 않습니다. let _ = five(); }
diagnostic 도구 속성 네임스페이스
#[diagnostic] 속성 네임스페이스는 컴파일 타임 에러 메시지에 영향을 주는 속성들을 위한 공간입니다. 이러한 속성들이 제공하는 힌트가 반드시 사용된다는 보장은 없습니다.
이 네임스페이스 내의 알 수 없는 속성들은 수용되지만, 사용되지 않는 속성에 대해 경고를 내보낼 수 있습니다. 추가적으로, 알려진 속성에 대한 유효하지 않은 입력은 보통 경고가 됩니다 (자세한 내용은 속성 정의를 참조하세요). 이는 의미 없는 속성이나 옵션들을 계속 유지할 필요 없이 미래에 속성을 추가하거나 제거하고 입력을 변경할 수 있도록 하기 위함입니다.
diagnostic::on_unimplemented 속성
#[diagnostic::on_unimplemented] 속성은 트레잇이 요구되지만 특정 타입에 구현되지 않은 상황에서 보통 생성되는 에러 메시지를 보완하도록 컴파일러에 주는 힌트입니다.
이 속성은 트레잇 선언 에 위치해야 하지만, 다른 위치에 있어도 에러는 아닙니다.
The attribute uses the MetaListNameValueStr syntax to specify its inputs, though any malformed input to the attribute is not considered as an error to provide both forwards and backwards compatibility.
다음 키들은 주어진 의미를 갖습니다:
message— 최상위 레벨 에러 메시지를 위한 텍스트입니다.label— 에러 메시지에서 잘못된 코드 내에 표시될 라벨을 위한 텍스트입니다.note— 추가적인 노트를 제공합니다.
note 옵션은 여러 번 나타날 수 있으며, 그 결과 여러 개의 노트 메시지가 내보내집니다.
다른 옵션들이 여러 번 나타나는 경우, 해당 옵션의 첫 번째 출현이 실제로 사용되는 값을 지정합니다. 이후의 출현은 경고를 발생시킵니다.
알 수 없는 키에 대해서는 경고가 발생합니다.
세 가지 옵션 모두 문자열을 인수로 받으며, std::fmt 문자열과 동일한 포맷팅 방식을 사용하여 해석됩니다.
주어진 이름을 가진 포맷 매개변수들은 다음 텍스트로 대체됩니다:
{Self}— 트레잇을 구현하는 타입의 이름입니다.{제네릭매개변수이름}— 주어진 제네릭 매개변수에 대한 제네릭 인수의 타입 이름입니다.
그 외의 포맷 매개변수는 경고를 발생시키지만, 문자열에는 그대로 포함됩니다.
유효하지 않은 포맷 문자열은 경고를 발생시킬 수 있지만, 허용은 됩니다. 다만 의도한 대로 표시되지 않을 수 있습니다. 포맷 지정자(Format specifiers)는 경고를 발생시킬 수 있지만, 무시됩니다.
이 예시에서:
#[diagnostic::on_unimplemented(
message = "`{Self}` 에 구현된 `ImportantTrait<{A}>` 에 대한 메시지",
label = "나의 라벨",
note = "노트 1",
note = "노트 2"
)]
trait ImportantTrait<A> {}
fn use_my_trait(_: impl ImportantTrait<i32>) {}
fn main() {
use_my_trait(String::new());
}
컴파일러는 다음과 같은 에러 메시지를 생성할 수 있습니다:
error[E0277]: My Message for `ImportantTrait<i32>` implemented for `String`
--> src/main.rs:14:18
|
14 | use_my_trait(String::new());
| ------------ ^^^^^^^^^^^^^ My Label
| |
| required by a bound introduced by this call
|
= help: the trait `ImportantTrait<i32>` is not implemented for `String`
= note: Note 1
= note: Note 2
diagnostic::do_not_recommend 속성
#[diagnostic::do_not_recommend] 속성은 진단 메시지의 일부로 해당 트레잇 구현을 표시하지 않도록 컴파일러에 주는 힌트입니다.
Note
Suppressing the recommendation can be useful if you know that the recommendation would normally not be useful to the programmer. This often occurs with broad, blanket impls. The recommendation may send the programmer down the wrong path, or the trait implementation may be an internal detail that you don’t want to expose, or the bounds may not be able to be satisfied by the programmer.
예를 들어, 요구되는 트레잇을 구현하지 않은 타입에 대한 에러 메시지에서, 컴파일러는 트레잇 구현 내의 특정 바운드만 아니었다면 요구 사항을 충족했을 트레잇 구현을 찾을 수도 있습니다. 컴파일러는 사용자에게 구현이 존재하지만 트레잇 구현 내의 바운드가 문제라고 말할 수 있습니다.
#[diagnostic::do_not_recommend]속성은 컴파일러에게 해당 트레잇 구현에 대해 사용자에게 알리지 말고, 대신 단순히 해당 타입이 요구되는 트레잇을 구현하지 않는다고 알리도록 하는 데 사용될 수 있습니다.
이 속성은 트레잇 구현 아이템 에 위치해야 하지만, 다른 위치에 있어도 에러는 아닙니다.
이 속성은 어떤 인수도 받지 않지만, 예상치 못한 인수가 있어도 에러로 간주되지 않습니다.
다음 예시에는 SQL 라이브러리에서 사용되는 Expression 타입으로 임의의 타입을 캐스팅하는 데 사용되는 AsExpression 이라는 트레잇이 있습니다. 여기에는 AsExpression 을 인수로 받는 check 라는 메서드가 있습니다.
pub trait Expression {
type SqlType;
}
pub trait AsExpression<ST> {
type Expression: Expression<SqlType = ST>;
}
pub struct Text;
pub struct Integer;
pub struct Bound<T>(T);
pub struct SelectInt;
impl Expression for SelectInt {
type SqlType = Integer;
}
impl<T> Expression for Bound<T> {
type SqlType = T;
}
impl AsExpression<Integer> for i32 {
type Expression = Bound<Integer>;
}
impl AsExpression<Text> for &'_ str {
type Expression = Bound<Text>;
}
impl<T> Foo for T where T: Expression {}
// 권장 사항을 변경하려면 이 라인의 주석을 해제하세요.
// #[diagnostic::do_not_recommend]
impl<T, ST> AsExpression<ST> for T
where
T: Expression<SqlType = ST>,
{
type Expression = T;
}
trait Foo: Expression + Sized {
fn check<T>(&self, _: T) -> <T as AsExpression<<Self as Expression>::SqlType>>::Expression
where
T: AsExpression<Self::SqlType>,
{
todo!()
}
}
fn main() {
SelectInt.check("bar");
}
SelectInt 타입의 check 메서드는 Integer 타입을 기대합니다. AsExpression 트레잇에 의해 i32 타입이 Integer 로 변환되므로 i32 타입으로 호출하는 것은 작동합니다. 하지만 문자열로 호출하는 것은 작동하지 않으며, 다음과 같은 에러를 발생시킬 수 있습니다.
error[E0277]: the trait bound `&str: Expression` is not satisfied
--> src/main.rs:53:15
|
53 | SelectInt.check("bar");
| ^^^^^ the trait `Expression` is not implemented for `&str`
|
= help: the following other types implement trait `Expression`:
Bound<T>
SelectInt
note: required for `&str` to implement `AsExpression<Integer>`
--> src/main.rs:45:13
|
45 | impl<T, ST> AsExpression<ST> for T
| ^^^^^^^^^^^^^^^^ ^
46 | where
47 | T: Expression<SqlType = ST>,
| ------------------------ unsatisfied trait bound introduced here
By adding the #[diagnostic::do_not_recommend] attribute to the blanket impl for AsExpression, the message changes to:
error[E0277]: the trait bound `&str: AsExpression<Integer>` is not satisfied
--> src/main.rs:53:15
|
53 | SelectInt.check("bar");
| ^^^^^ the trait `AsExpression<Integer>` is not implemented for `&str`
|
= help: the trait `AsExpression<Integer>` is not implemented for `&str`
but trait `AsExpression<Text>` is implemented for it
= help: for that trait implementation, expected `Text`, found `Integer`
The first error message includes a somewhat confusing error message about the relationship of &str and Expression, as well as the unsatisfied trait bound in the blanket impl. After adding #[diagnostic::do_not_recommend], it no longer considers the blanket impl for the recommendation. The message should be a little clearer, with an indication that a string cannot be converted to an Integer.
코드 생성 속성
다음 속성들 은 코드 생성을 제어하는 데 사용됩니다.
inline 속성
The inline attribute suggests whether a copy of the attributed function’s code should be placed in the caller rather than generating a call to the function.
Example
#![allow(unused)] fn main() { #[inline] pub fn example1() {} #[inline(always)] pub fn example2() {} #[inline(never)] pub fn example3() {} }
Note
rustcautomatically inlines functions when doing so seems worthwhile. Use this attribute carefully as poor decisions about what to inline can slow down programs.
The syntax for the inline attribute is:
Syntax
InlineAttribute →
inline ( always )
| inline ( never )
| inline
The inline attribute may only be applied to functions with bodies — closures, async blocks, free functions, associated functions in an inherent impl or trait impl, and associated functions in a trait definition when those functions have a default definition .
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
Note
Though the attribute can be applied to closures and async blocks, the usefulness of this is limited as we do not yet support attributes on expressions.
#![allow(unused)] fn main() { // We allow attributes on statements. #[inline] || (); // OK #[inline] async {}; // OK }#![allow(unused)] fn main() { // We don't yet allow attributes on expressions. let f = #[inline] || (); // 오류 }
Only the first use of inline on a function has effect.
Note
rustclints against any use following the first. This may become an error in the future.
The inline attribute supports these modes:
#[inline]suggests performing inline expansion.#[inline(always)]suggests that inline expansion should always be performed.#[inline(never)]suggests that inline expansion should never be performed.
Note
In every form the attribute is a hint. The compiler may ignore it.
When inline is applied to a function in a trait, it applies only to the code of the default definition.
When inline is applied to an async function or async closure, it applies only to the code of the generated poll function.
Note
For more details, see Rust issue #129347.
The inline attribute is ignored if the function is externally exported with no_mangle or export_name.
cold 속성
The cold attribute suggests that the attributed function is unlikely to be called which may help the compiler produce better code.
Example
#![allow(unused)] fn main() { #[cold] pub fn example() {} }
The cold attribute uses the MetaWord syntax.
The cold attribute may only be applied to functions with bodies — closures, async blocks, free functions, associated functions in an inherent impl or trait impl, and associated functions in a trait definition when those functions have a default definition .
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
Note
Though the attribute can be applied to closures and async blocks, the usefulness of this is limited as we do not yet support attributes on expressions.
Only the first use of cold on a function has effect.
Note
rustclints against any use following the first. This may become an error in the future.
When cold is applied to a function in a trait, it applies only to the code of the default definition.
The naked attribute
The naked attribute prevents the compiler from emitting a function prologue and epilogue for the attributed function.
The function body must consist of exactly one naked_asm! macro invocation.
No function prologue or epilogue is generated for the attributed function. The assembly code in the naked_asm! block constitutes the full body of a naked function.
The naked attribute is an unsafe attribute. Annotating a function with #[unsafe(naked)] comes with the safety obligation that the body must respect the function’s calling convention, uphold its signature, and either return or diverge (i.e., not fall through past the end of the assembly code).
The assembly code may assume that the call stack and register state are valid on entry as per the signature and calling convention of the function.
The assembly code may not be duplicated by the compiler except when monomorphizing polymorphic functions.
Note
Guaranteeing when the assembly code may or may not be duplicated is important for naked functions that define symbols.
The unused_variables lint is suppressed within naked functions.
The inline attribute cannot by applied to a naked function.
The track_caller attribute cannot be applied to a naked function.
The testing attributes cannot be applied to a naked function.
no_builtins 속성
The no_builtins attribute disables optimization of certain code patterns related to calls to library functions that are assumed to exist.
Example
#![allow(unused)] #![no_builtins] fn main() { }
The no_builtins attribute uses the MetaWord syntax.
The no_builtins attribute can only be applied to the crate root.
Only the first use of the no_builtins attribute has effect.
Note
rustclints against any use following the first.
target_feature 속성
The target_feature attribute may be applied to a function to enable code generation of that function for specific platform architecture features. It uses the MetaListNameValueStr syntax with a single key of enable whose value is a string of comma-separated feature names to enable.
#![allow(unused)]
fn main() {
#[cfg(target_feature = "avx2")]
#[target_feature(enable = "avx2")]
fn foo_avx2() {}
}
각 타겟 아키텍처 는 활성화할 수 있는 일련의 기능들을 가지고 있습니다. 크레이트가 컴파일되지 않는 타겟 아키텍처에 대한 기능을 지정하는 것은 에러입니다.
target_feature 가 주석 처리된 함수 내에 정의된 클로저는 둘러싼 함수의 속성을 상속받습니다.
코드가 실행 중인 현재 플랫폼에서 지원하지 않는 기능으로 컴파일된 함수를 호출하는 것은 정의되지 않은 동작(undefined behavior) 입니다. 단, 플랫폼에서 이를 안전하다고 명시적으로 문서화한 경우는 예외입니다.
아래 플랫폼 규칙에서 달리 명시하지 않는 한 다음 제약 사항이 적용됩니다.
- 안전한(safe)
#[target_feature]함수(및 속성을 상속받은 클로저)는 호출자가 피호출자가 활성화한 모든target_feature를 활성화한 경우에만 안전하게 호출될 수 있습니다. 이 제약 사항은unsafe컨텍스트에서는 적용되지 않습니다. - 안전한
#[target_feature]함수(및 속성을 상속받은 클로저)는 강제 변환되는 위치가 피강제변환자가 활성화한 모든target_feature를 활성화한 컨텍스트인 경우에만 안전한 함수 포인터로 강제 변환될 수 있습니다. 이 제약 사항은unsafe함수 포인터에는 적용되지 않습니다.
암시적으로 활성화된 기능들도 이 규칙에 포함됩니다. 예를 들어 sse2 함수는 sse 로 표시된 함수를 호출할 수 있습니다.
#![allow(unused)]
fn main() {
#[cfg(target_feature = "sse2")] {
#[target_feature(enable = "sse")]
fn foo_sse() {}
fn bar() {
// 여기서 `foo_sse` 를 호출하는 것은 안전하지 않습니다. 비록 타겟 플랫폼에서
// `sse` 가 기본적으로 활성화되어 있거나 컴파일러 플래그로 수동 활성화되어 있더라도,
// 먼저 SSE를 사용할 수 있는지 확인해야 하기 때문입니다.
unsafe {
foo_sse();
}
}
#[target_feature(enable = "sse")]
fn bar_sse() {
// 여기서 `foo_sse` 를 호출하는 것은 안전합니다.
foo_sse();
|| foo_sse();
}
#[target_feature(enable = "sse2")]
fn bar_sse2() {
// `sse2` 는 `sse` 를 포함하므로 여기서 `foo_sse` 를 호출하는 것은 안전합니다.
foo_sse();
}
}
}
#[target_feature] 속성이 있는 함수는 절대 Fn 계열 트레잇을 구현하지 않습니다. 단, 둘러싼 함수로부터 기능을 상속받은 클로저는 예외입니다.
#[target_feature] 속성은 다음 위치에서 허용되지 않습니다:
main함수- a
panic_handlerfunction - 안전한 트레잇 메서드
- 트레잇의 안전한 기본 함수
target_feature 가 표시된 함수는 주어진 기능을 지원하지 않는 컨텍스트로 인라인화되지 않습니다. #[inline(always)] 속성은 target_feature 속성과 함께 사용될 수 없습니다.
사용 가능한 기능
다음은 사용 가능한 기능 이름의 목록입니다.
x86 또는 x86_64
지원하지 않는 기능으로 코드를 실행하는 것은 이 플랫폼에서 정의되지 않은 동작입니다. 따라서 이 플랫폼에서 #[target_feature] 함수의 사용은 위의 제약 사항 을 따릅니다.
| 기능(Feature) | 암시적으로 활성화함 | 설명 |
|---|---|---|
adx | ADX — 다중 정밀도 덧셈-올림수(Multi-Precision Add-Carry) 명령어 확장 | |
aes | sse2 | AES — 고급 암호화 표준(Advanced Encryption Standard) |
avx | sse4.2 | AVX — 고급 벡터 확장(Advanced Vector Extensions) |
avx2 | avx | AVX2 — 고급 벡터 확장 2(Advanced Vector Extensions 2) |
avx512bf16 | avx512bw | AVX512-BF16 — Advanced Vector Extensions 512-bit - Bfloat16 Extensions |
avx512bitalg | avx512bw | AVX512-BITALG — Advanced Vector Extensions 512-bit - Bit Algorithms |
avx512bw | avx512f | AVX512-BW — Advanced Vector Extensions 512-bit - Byte and Word Instructions |
avx512cd | avx512f | AVX512-CD — Advanced Vector Extensions 512-bit - Conflict Detection Instructions |
avx512dq | avx512f | AVX512-DQ — Advanced Vector Extensions 512-bit - Doubleword and Quadword Instructions |
avx512f | avx2, fma, f16c | AVX512-F — Advanced Vector Extensions 512-bit - Foundation |
avx512fp16 | avx512bw | AVX512-FP16 — Advanced Vector Extensions 512-bit - Float16 Extensions |
avx512ifma | avx512f | AVX512-IFMA — Advanced Vector Extensions 512-bit - Integer Fused Multiply Add |
avx512vbmi | avx512bw | AVX512-VBMI — Advanced Vector Extensions 512-bit - Vector Byte Manipulation Instructions |
avx512vbmi2 | avx512bw | AVX512-VBMI2 — Advanced Vector Extensions 512-bit - Vector Byte Manipulation Instructions 2 |
avx512vl | avx512f | AVX512-VL — Advanced Vector Extensions 512-bit - Vector Length Extensions |
avx512vnni | avx512f | AVX512-VNNI — Advanced Vector Extensions 512-bit - Vector Neural Network Instructions |
avx512vp2intersect | avx512f | AVX512-VP2INTERSECT — Advanced Vector Extensions 512-bit - Vector Pair Intersection to a Pair of Mask Registers |
avx512vpopcntdq | avx512f | AVX512-VPOPCNTDQ — Advanced Vector Extensions 512-bit - Vector Population Count Instruction |
avxifma | avx2 | AVX-IFMA — Advanced Vector Extensions - Integer Fused Multiply Add |
avxneconvert | avx2 | AVX-NE-CONVERT — Advanced Vector Extensions - No-Exception Floating-Point conversion Instructions |
avxvnni | avx2 | AVX-VNNI — Advanced Vector Extensions - Vector Neural Network Instructions |
avxvnniint16 | avx2 | AVX-VNNI-INT16 — Advanced Vector Extensions - Vector Neural Network Instructions with 16-bit Integers |
avxvnniint8 | avx2 | AVX-VNNI-INT8 — Advanced Vector Extensions - Vector Neural Network Instructions with 8-bit Integers |
bmi1 | BMI1 — 비트 조작 명령어 집합(Bit Manipulation Instruction Sets) | |
bmi2 | BMI2 — 비트 조작 명령어 집합 2(Bit Manipulation Instruction Sets 2) | |
cmpxchg16b | cmpxchg16b — 16바이트(128비트) 데이터를 원자적으로 비교 및 교환 | |
f16c | avx | F16C — 16비트 부동 소수점 변환 명령어 |
fma | avx | FMA3 — 3-피연산자 융합 곱셈-더하기(Three-operand fused multiply-add) |
fxsr | fxsave 및 fxrstor — x87 FPU, MMX 기술 및 SSE 상태 저장 및 복원 | |
gfni | sse2 | GFNI — Galois Field New Instructions |
kl | sse2 | KEYLOCKER — Intel Key Locker Instructions |
lzcnt | lzcnt — 선행 제로 카운트(Leading zeros count) | |
movbe | movbe — 바이트 스왑 후 데이터 이동 | |
pclmulqdq | sse2 | pclmulqdq — 팩형 올림수 없는 곱셈 쿼드워드(Packed carry-less multiplication quadword) |
popcnt | popcnt — 1로 설정된 비트의 개수 | |
rdrand | rdrand — 난수 읽기 | |
rdseed | rdseed — 난수 시드 읽기 | |
sha | sse2 | SHA — 안전한 해시 알고리즘(Secure Hash Algorithm) |
sha512 | avx2 | SHA512 — Secure Hash Algorithm with 512-bit digest |
sm3 | avx | SM3 — ShangMi 3 Hash Algorithm |
sm4 | avx2 | SM4 — ShangMi 4 Cipher Algorithm |
sse | SSE — 스트리밍 SIMD 확장 | |
sse2 | sse | SSE2 — 스트리밍 SIMD 확장 2 |
sse3 | sse2 | SSE3 — 스트리밍 SIMD 확장 3 |
sse4.1 | ssse3 | SSE4.1 — 스트리밍 SIMD 확장 4.1 |
sse4.2 | sse4.1 | SSE4.2 — 스트리밍 SIMD 확장 4.2 |
sse4a | sse3 | SSE4a — Streaming SIMD Extensions 4a |
ssse3 | sse3 | SSSE3 — Supplemental Streaming SIMD Extensions 3 |
tbm | TBM — Trailing Bit Manipulation | |
vaes | avx2, aes | VAES — Vector AES Instructions |
vpclmulqdq | avx, pclmulqdq | VPCLMULQDQ — Vector Carry-less multiplication of Quadwords |
widekl | kl | KEYLOCKER_WIDE — Intel Wide Keylocker Instructions |
xsave | xsave — 프로세서 확장 상태 저장 | |
xsavec | xsavec — 압축을 사용한 프로세서 확장 상태 저장 | |
xsaveopt | xsaveopt — 최적화된 프로세서 확장 상태 저장 | |
xsaves | xsaves — 관리자용 프로세서 확장 상태 저장 |
aarch64
이 플랫폼에서 #[target_feature] 함수의 사용은 위의 제약 사항 을 따릅니다.
이러한 기능들에 대한 추가 문서는 ARM 아키텍처 참조 매뉴얼 또는 developer.arm.com 에서 찾을 수 있습니다.
Note
The following pairs of features should both be marked as enabled or disabled together if used:
paca및pacg(LLVM은 현재 이를 하나의 기능으로 구현함).
| 기능(Feature) | 암시적으로 활성화함 | 기능 이름 |
|---|---|---|
aes | neon | FEAT_AES & FEAT_PMULL — 고급 SIMD AES 및 PMULL 명령어 |
bf16 | FEAT_BF16 — BFloat16 명령어 | |
bti | FEAT_BTI — 분기 타겟 식별(Branch Target Identification) | |
crc | FEAT_CRC — CRC32 체크섬 명령어 | |
dit | FEAT_DIT — Data Independent Timing instructions | |
dotprod | neon | FEAT_DotProd — 고급 SIMD Int8 내적(dot product) 명령어 |
dpb | FEAT_DPB — 영속성 지점까지의 데이터 캐시 클린(Data cache clean to point of persistence) | |
dpb2 | dpb | FEAT_DPB2 — 깊은 영속성 지점까지의 데이터 캐시 클린(Data cache clean to point of deep persistence) |
f32mm | sve | FEAT_F32MM — SVE 단정밀도 부동 소수점 행렬 곱셈 명령어 |
f64mm | sve | FEAT_F64MM — SVE 배정밀도 부동 소수점 행렬 곱셈 명령어 |
fcma | neon | FEAT_FCMA — 부동 소수점 복소수 지원 |
fhm | fp16 | FEAT_FHM — 반정밀도 부동 소수점 FMLAL 명령어 |
flagm | FEAT_FLAGM — Conditional flag manipulation | |
fp16 | neon | FEAT_FP16 — 반정밀도 부동 소수점 데이터 처리 |
frintts | FEAT_FRINTTS — 부동 소수점에서 정수로의 변환 도우미 명령어 | |
i8mm | FEAT_I8MM — Int8 행렬 곱셈 | |
jsconv | neon | FEAT_JSCVT — 자바스크립트 변환 명령어 |
lor | FEAT_LOR — 제한된 순서 영역(Limited Ordering Regions) 확장 | |
lse | FEAT_LSE — Large System Extensions | |
mte | FEAT_MTE & FEAT_MTE2 — 메모리 태깅 확장(Memory Tagging Extension) | |
neon | FEAT_AdvSimd & FEAT_FP — Floating Point and Advanced SIMD extension | |
paca | FEAT_PAUTH — Pointer Authentication (address authentication) | |
pacg | FEAT_PAUTH — Pointer Authentication (generic authentication) | |
pan | FEAT_PAN — 특권층 액세스 금지(Privileged Access-Never) 확장 | |
pmuv3 | FEAT_PMUv3 — 성능 모니터 확장 (v3) | |
rand | FEAT_RNG — 난수 생성기(Random Number Generator) | |
ras | FEAT_RAS & FEAT_RASv1p1 — 신뢰성, 가용성 및 서비스성(Reliability, Availability and Serviceability) 확장 | |
rcpc | FEAT_LRCPC — 릴리스 일관성 프로세서 일관성(Release consistent Processor Consistent) | |
rcpc2 | rcpc | FEAT_LRCPC2 — 즉시 오프셋을 포함한 RcPc |
rdm | neon | FEAT_RDM — 반올림 이중 곱셈 누산(Rounding Double Multiply accumulate) |
sb | FEAT_SB — 투기적 실행 배리어(Speculation Barrier) | |
sha2 | neon | FEAT_SHA1 & FEAT_SHA256 — 고급 SIMD SHA 명령어 |
sha3 | sha2 | FEAT_SHA512 & FEAT_SHA3 — 고급 SIMD SHA 명령어 |
sm4 | neon | FEAT_SM3 & FEAT_SM4 — 고급 SIMD SM3/4 명령어 |
spe | FEAT_SPE — 통계적 프로파일링 확장(Statistical Profiling Extension) | |
ssbs | FEAT_SSBS & FEAT_SSBS2 — 투기적 저장소 우회 안전(Speculative Store Bypass Safe) | |
sve | neon | FEAT_SVE — 가변 벡터 확장(Scalable Vector Extension) |
sve2 | sve | FEAT_SVE2 — 가변 벡터 확장 2 |
sve2-aes | sve2, aes | FEAT_SVE_AES & FEAT_SVE_PMULL128 — SVE AES instructions |
sve2-bitperm | sve2 | FEAT_SVE2_BitPerm — SVE Bit Permute |
sve2-sha3 | sve2, sha3 | FEAT_SVE2_SHA3 — SVE SHA3 instructions |
sve2-sm4 | sve2, sm4 | FEAT_SVE2_SM4 — SVE SM4 instructions |
tme | FEAT_TME — 트랜잭셔널 메모리 확장(Transactional Memory Extension) | |
vh | FEAT_VHE — 가상화 호스트 확장(Virtualization Host Extensions) |
loongarch
이 플랫폼에서 #[target_feature] 함수의 사용은 위의 제약 사항 을 따릅니다.
| 기능(Feature) | 암시적으로 활성화함 | 설명 |
|---|---|---|
f | F — Single-precision float-point instructions | |
d | f | D — Double-precision float-point instructions |
frecipe | FRECIPE — Reciprocal approximation instructions | |
lasx | lsx | LASX — 256-bit vector instructions |
lbt | LBT — Binary translation instructions | |
lsx | d | LSX — 128-bit vector instructions |
lvz | LVZ — Virtualization instructions |
riscv32 또는 riscv64
이 플랫폼에서 #[target_feature] 함수의 사용은 위의 제약 사항 을 따릅니다.
Further documentation on these features can be found in their respective specification. Many specifications are described in the RISC-V ISA Manual, version 20250508, or in another manual hosted on the RISC-V GitHub Account.
| 기능(Feature) | 암시적으로 활성화함 | 설명 |
|---|---|---|
a | zaamo, zalrsc | A — Atomic instructions |
b | zba, zbc, zbs | B — Bit Manipulation instructions |
c | zca | C — Compressed instructions |
m | M — Integer Multiplication and Division instructions | |
za64rs | za128rs | Za64rs — Platform Behavior: Naturally aligned Reservation sets with ≦ 64 Bytes |
za128rs | Za128rs — Platform Behavior: Naturally aligned Reservation sets with ≦ 128 Bytes | |
zaamo | Zaamo — Atomic Memory Operation instructions | |
zabha | zaamo | Zabha — Byte and Halfword Atomic Memory Operation instructions |
zacas | zaamo | Zacas — Atomic Compare-and-Swap (CAS) instructions |
zalrsc | Zalrsc — Load-Reserved/Store-Conditional instructions | |
zama16b | Zama16b — Platform Behavior: Misaligned loads, stores, and AMOs to main memory regions that do not cross a naturally aligned 16-byte boundary are atomic | |
zawrs | Zawrs — Wait-on-Reservation-Set instructions | |
zba | Zba — Address Generation instructions | |
zbb | Zbb — Basic bit-manipulation | |
zbc | zbkc | Zbc — Carry-less multiplication |
zbkb | Zbkb — Bit Manipulation Instructions for Cryptography | |
zbkc | Zbkc — Carry-less multiplication for Cryptography | |
zbkx | Zbkx — Crossbar permutations | |
zbs | Zbs — Single-bit instructions | |
zca | Zca — Compressed instructions: integer part subset | |
zcb | zca | Zcb — Simple Code-size Saving Compressed instructions |
zcmop | zca | Zcmop — Compressed May-Be-Operations |
zic64b | Zic64b — Platform Behavior: Naturally aligned 64 byte Cache blocks | |
zicbom | Zicbom — Cache-Block Management instructions | |
zicbop | Zicbop — Cache-Block Prefetch Hint instructions | |
zicboz | Zicboz — Cache-Block Zero instruction | |
ziccamoa | Ziccamoa — Platform Behavior: Cacheable and Coherent Main memory supports all basic atomic operations | |
ziccif | Ziccif — Platform Behavior: Cacheable and Coherent Main memory supports instruction fetch and fetches of naturally aligned power-of-2 sizes up to min(ILEN,XLEN) are atomic | |
zicclsm | Zicclsm — Platform Behavior: Cacheable and Coherent Main memory supports misaligned load/store accesses | |
ziccrse | Ziccrse — Platform Behavior: Cacheable and Coherent Main memory guarantees eventual success on LR/SC sequences | |
zicntr | zicsr | Zicntr — Base Counters and Timers |
zicond | Zicond — Integer Conditional Operation instructions | |
zicsr | Zicsr — Control and Status Register (CSR) instructions | |
zifencei | Zifencei — Instruction-Fetch Fence instruction | |
zihintntl | Zihintntl — Non-Temporal Locality Hint instructions | |
zihintpause | Zihintpause — Pause Hint instruction | |
zihpm | zicsr | Zihpm — Hardware Performance Counters |
zimop | Zimop — May-Be-Operations | |
zk | zkn, zkr, zks, zkt, zbkb, zbkc, zkbx | Zk — Scalar Cryptography |
zkn | zknd, zkne, zknh, zbkb, zbkc, zkbx | Zkn — NIST Algorithm suite extension |
zknd | Zknd — NIST Suite: AES Decryption | |
zkne | Zkne — NIST Suite: AES Encryption | |
zknh | Zknh — NIST Suite: Hash Function Instructions | |
zkr | Zkr — Entropy Source Extension | |
zks | zksed, zksh, zbkb, zbkc, zkbx | Zks — ShangMi Algorithm Suite |
zksed | Zksed — ShangMi Suite: SM4 Block Cipher Instructions | |
zksh | Zksh — ShangMi Suite: SM3 Hash Function Instructions | |
zkt | Zkt — Data Independent Execution Latency Subset | |
ztso | Ztso — Total Store Ordering |
wasm32 또는 wasm64
Wasm 플랫폼에서 안전한(safe) #[target_feature] 함수는 항상 안전한 컨텍스트에서 사용될 수 있습니다. #[target_feature] 속성을 통해 정의되지 않은 동작을 유발하는 것은 불가능합니다. 왜냐하면 Wasm 엔진이 지원하지 않는 명령어를 사용하려는 시도는 로드 시점에 실패하며, 컴파일러가 예상한 것과 다르게 해석될 위험이 없기 때문입니다.
| 기능(Feature) | 암시적으로 활성화함 | 설명 |
|---|---|---|
bulk-memory | WebAssembly 대량 메모리 연산 제안(bulk memory operations proposal) | |
extended-const | WebAssembly 확장 상수 표현식 제안(extended const expressions proposal) | |
mutable-globals | WebAssembly 가변 전역 변수 제안(mutable global proposal) | |
nontrapping-fptoint | WebAssembly 비-트래핑 부동 소수점-정수 변환 제안(non-trapping float-to-int conversion proposal) | |
relaxed-simd | simd128 | WebAssembly relaxed SIMD 제안 |
sign-ext | WebAssembly 부호 확장 연산자 제안(sign extension operators Proposal) | |
simd128 | WebAssembly SIMD 제안 | |
multivalue | WebAssembly multivalue 제안 | |
reference-types | WebAssembly reference-types 제안 | |
tail-call | WebAssembly tail-call 제안 |
s390x
On s390x targets, use of functions with the #[target_feature] attribute follows the above restrictions.
Further documentation on these features can be found in the “Additions to z/Architecture” section of Chapter 1 of the z/Architecture Principles of Operation.
| 기능(Feature) | 암시적으로 활성화함 | 설명 |
|---|---|---|
vector | 128-bit vector instructions | |
vector-enhancements-1 | vector | vector enhancements 1 |
vector-enhancements-2 | vector-enhancements-1 | vector enhancements 2 |
vector-enhancements-3 | vector-enhancements-2 | vector enhancements 3 |
vector-packed-decimal | vector | vector packed-decimal |
vector-packed-decimal-enhancement | vector-packed-decimal | vector packed-decimal enhancement |
vector-packed-decimal-enhancement-2 | vector-packed-decimal-enhancement-2 | vector packed-decimal enhancement 2 |
vector-packed-decimal-enhancement-3 | vector-packed-decimal-enhancement-3 | vector packed-decimal enhancement 3 |
nnp-assist | vector | nnp assist |
miscellaneous-extensions-2 | miscellaneous extensions 2 | |
miscellaneous-extensions-3 | miscellaneous extensions 3 | |
miscellaneous-extensions-4 | miscellaneous extensions 4 |
추가 정보
컴파일 타임 설정에 기반하여 코드 컴파일을 선택적으로 활성화하거나 비활성화하려면 target_feature 조건부 컴파일 옵션 을 참조하세요. 이 옵션은 target_feature 속성의 영향을 받지 않으며, 오직 크레이트 전체에 대해 활성화된 기능들에 의해서만 구동된다는 점에 유의하세요.
Whether a feature is enabled can be checked at runtime using a platform-specific macro from the standard library, for instance is_x86_feature_detected or is_aarch64_feature_detected.
Note
rustchas a default set of features enabled for each target and CPU. The CPU may be chosen with the-C target-cpuflag. Individual features may be enabled or disabled for an entire crate with the-C target-featureflag.
track_caller 속성
track_caller 속성은 fn main 엔트리 포인트를 제외하고, "Rust" ABI 를 가진 모든 함수에 적용될 수 있습니다.
트레잇 선언의 함수와 메서드에 적용될 경우, 이 속성은 모든 구현체에 적용됩니다. 만약 트레잇이 속성과 함께 기본 구현을 제공한다면, 이 속성은 오버라이드된 구현체들에도 적용됩니다.
extern 블록 내의 함수에 적용될 경우, 이 속성은 연결된 모든 구현체에도 반드시 적용되어야 합니다. 그렇지 않으면 정의되지 않은 동작(undefined behavior)이 발생합니다. extern 블록에서 사용 가능하도록 만들어진 함수에 적용될 경우, extern 블록 내의 선언부에도 반드시 이 속성이 있어야 합니다. 그렇지 않으면 정의되지 않은 동작이 발생합니다.
동작
함수 f 에 이 속성을 적용하면 f 내부의 코드가 f 를 호출하게 만든 “최상위” 추적 호출의 Location 힌트를 얻을 수 있게 됩니다. 관찰 시점에, 구현체는 f 의 프레임에서 스택을 거슬러 올라가 속성이 붙지 않은 가장 가까운 함수 outer 의 프레임을 찾고, outer 에서의 추적된 호출 위치(Location)를 반환하는 것처럼 동작합니다.
#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
println!("{}", std::panic::Location::caller());
}
}
Note
coreprovidescore::panic::Location::callerfor observing caller locations. It wraps thecore::intrinsics::caller_locationintrinsic implemented byrustc.
Note
Because the resulting
Locationis a hint, an implementation may halt its walk up the stack early. See Limitations for important caveats.
예시
f 가 calls_f 에 의해 직접 호출될 때, f 내부의 코드는 calls_f 내의 호출 지점(callsite)을 관찰합니다.
#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
println!("{}", std::panic::Location::caller());
}
fn calls_f() {
f(); // <-- f()가 이 위치를 출력함
}
}
f 가 속성이 붙은 다른 함수 g 에 의해 호출되고, g 가 다시 calls_g 에 의해 호출될 때, f 와 g 모두의 코드는 calls_g 내의 g 호출 지점을 관찰합니다.
#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
println!("{}", std::panic::Location::caller());
}
#[track_caller]
fn g() {
println!("{}", std::panic::Location::caller());
f();
}
fn calls_g() {
g(); // <-- g()가 이 위치를 두 번 출력함 (한 번은 자기 자신, 한 번은 f()로부터)
}
}
g 가 속성이 붙은 다른 함수 h 에 의해 호출되고, h 가 다시 calls_h 에 의해 호출될 때, f, g, h 내부의 모든 코드는 calls_h 내의 h 호출 지점을 관찰합니다.
#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
println!("{}", std::panic::Location::caller());
}
#[track_caller]
fn g() {
println!("{}", std::panic::Location::caller());
f();
}
#[track_caller]
fn h() {
println!("{}", std::panic::Location::caller());
g();
}
fn calls_h() {
h(); // <-- 이 위치를 세 번 출력함 (한 번은 자기 자신, 한 번은 g()로부터, 한 번은 f()로부터)
}
}
기타 등등.
제한 사항
이 정보는 힌트일 뿐이며 구현체가 이를 반드시 보존해야 할 요구 사항은 없습니다.
특히, #[track_caller] 가 붙은 함수를 함수 포인터로 강제 변환하면 심(shim)이 생성됩니다. 이 심은 관찰자에게 해당 함수가 정의된 위치에서 호출된 것처럼 보이게 하여, 가상 호출(virtual calls) 시 실제 호출자 정보를 잃게 만듭니다. 이러한 강제 변환의 흔한 예시는 메서드에 속성이 붙은 트레잇 객체를 생성하는 경우입니다.
Note
The aforementioned shim for function pointers is necessary because
rustcimplementstrack_callerin a codegen context by appending an implicit parameter to the function ABI, but this would be unsound for an indirect call because the parameter is not a part of the function’s type and a given function pointer type may or may not refer to a function with the attribute. The creation of a shim hides the implicit parameter from callers of the function pointer, preserving soundness.
instruction_set 속성
The instruction_set attribute specifies the instruction set that a function will use during code generation. This allows mixing more than one instruction set in a single program.
Example
#[instruction_set(arm::a32)] fn arm_code() {} #[instruction_set(arm::t32)] fn thumb_code() {}
The instruction_set attribute uses the MetaListPaths syntax to specify a single path consisting of the architecture family name and instruction set name.
The instruction_set attribute may only be applied to functions with bodies — closures, async blocks, free functions, associated functions in an inherent impl or trait impl, and associated functions in a trait definition when those functions have a default definition .
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
Note
Though the attribute can be applied to closures and async blocks, the usefulness of this is limited as we do not yet support attributes on expressions.
The instruction_set attribute may be used only once on a function.
The instruction_set attribute may only be used with a target that supports the given value.
When the instruction_set attribute is used, any inline assembly in the function must use the specified instruction set instead of the target default.
instruction_set on ARM
When targeting the ARMv4T and ARMv5te architectures, the supported values for instruction_set are:
arm::a32— 함수를 A32 “ARM” 코드로 생성합니다.arm::t32— 함수를 T32 “Thumb” 코드로 생성합니다.
If the address of the function is taken as a function pointer, the low bit of the address will depend on the selected instruction set:
- For
arm::a32(“ARM”), it will be 0. - For
arm::t32(“Thumb”), it will be 1.
제한
다음 속성들 은 컴파일 타임 제한에 영향을 줍니다.
recursion_limit 속성
recursion_limit 속성 은 매크로 확장이나 자동 역참조(auto-dereference)와 같이 잠재적으로 무한히 재귀적인 컴파일 타임 연산의 최대 깊이를 설정하기 위해 크레이트 레벨에 적용될 수 있습니다.
It uses the MetaNameValueStr syntax to specify the recursion depth.
Note
The default in
rustcis 128.
#![allow(unused)]
#![recursion_limit = "4"]
fn main() {
macro_rules! a {
() => { a!(1); };
(1) => { a!(2); };
(2) => { a!(3); };
(3) => { a!(4); };
(4) => { };
}
// 이는 4보다 큰 재귀 깊이를 요구하므로 확장에 실패합니다.
a!{}
}
#![allow(unused)]
#![recursion_limit = "1"]
fn main() {
// 이는 자동 역참조를 위해 두 번의 재귀 단계가 필요하므로 실패합니다.
(|_: &u8| {})(&&&1);
}
type_length_limit 속성
The type_length_limit attribute sets the maximum number of type substitutions allowed when constructing a concrete type during monomorphization.
Note
rustconly enforces the limit when the nightly-Zenforce-type-length-limitflag is active.For more information, see Rust PR #127670.
Example
#![type_length_limit = "4"] fn f<T>(x: T) {} // This fails to compile because monomorphizing to // `f::<((((i32,), i32), i32), i32)>` requires more // than 4 type elements. f(((((1,), 2), 3), 4));
Note
The default value in
rustcis1048576.
The type_length_limit attribute uses the MetaNameValueStr syntax. The value in the string must be a non-negative number.
The type_length_limit attribute may only be applied to the crate root.
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
Only the first use of type_length_limit on an item has effect.
Note
rustclints against any use following the first. This may become an error in the future.
타입 시스템 속성
다음 속성들 은 타입이 사용되는 방식을 변경하는 데 사용됩니다.
non_exhaustive 속성
non_exhaustive 속성 은 타입이나 변형에 미래에 더 많은 필드나 변형이 추가될 수 있음을 나타냅니다.
이 속성은 구조체, 열거형, 그리고 열거형 변형에 적용될 수 있습니다.
The non_exhaustive attribute uses the MetaWord syntax and thus does not take any inputs.
정의하는 크레이트 내에서 non_exhaustive 는 아무런 효과가 없습니다.
#![allow(unused)]
fn main() {
#[non_exhaustive]
pub struct Config {
pub window_width: u16,
pub window_height: u16,
}
#[non_exhaustive]
pub struct Token;
#[non_exhaustive]
pub struct Id(pub u64);
#[non_exhaustive]
pub enum Error {
Message(String),
Other,
}
pub enum Message {
#[non_exhaustive] Send { from: u32, to: u32, contents: String },
#[non_exhaustive] Reaction(u32),
#[non_exhaustive] Quit,
}
// 비완전(non-exhaustive) 구조체는 정의하는 크레이트 내에서 평소와 같이 생성될 수 있습니다.
let config = Config { window_width: 640, window_height: 480 };
let token = Token;
let id = Id(4);
// 비완전 구조체는 정의하는 크레이트 내에서 완전하게(exhaustively) 매칭될 수 있습니다.
let Config { window_width, window_height } = config;
let Token = token;
let Id(id_number) = id;
let error = Error::Other;
let message = Message::Reaction(3);
// 비완전 열거형은 정의하는 크레이트 내에서 완전하게 매칭될 수 있습니다.
match error {
Error::Message(ref s) => { },
Error::Other => { },
}
match message {
// 비완전 변형은 정의하는 크레이트 내에서 완전하게 매칭될 수 있습니다.
Message::Send { from, to, contents } => { },
Message::Reaction(id) => { },
Message::Quit => { },
}
}
정의하는 크레이트 외부에서, non_exhaustive 가 주석 처리된 타입은 새로운 필드나 변형이 추가될 때 하위 호환성을 유지하기 위한 제약 사항을 가집니다.
비완전 타입은 정의하는 크레이트 외부에서 생성될 수 없습니다.
- Non-exhaustive variants (
structorenumvariant) cannot be constructed with a StructExpression (including with functional update syntax). - 유닛 유사 구조체 의 암시적으로 정의된 동일 이름의 상수나, 튜플 구조체 의 동일 이름의 생성자 함수는
pub(crate)보다 크지 않은 가시성 을 가집니다. 즉, 구조체의 가시성이pub인 경우 상수나 생성자의 가시성은pub(crate)가 되며, 그 외의 경우 두 아이템의 가시성은 동일합니다 (#[non_exhaustive]가 없는 경우와 같습니다). 열거형(enum)인스턴스는 생성될 수 있습니다.
정의하는 크레이트 외부에서는 다음과 같은 생성 예시들이 컴파일되지 않습니다.
// 이들은 업스트림 크레이트에서 `#[non_exhaustive]` 가 주석 처리된 상태로 정의된 타입들입니다.
use upstream::{Config, Token, Id, Error, Message};
// `Config` 인스턴스를 생성할 수 없습니다. 만약 업스트림의 새로운 버전에서
// 새로운 필드가 추가된다면 컴파일에 실패할 것이므로, 이를 불허합니다.
let config = Config { window_width: 640, window_height: 480 };
// `Token` 인스턴스를 생성할 수 없습니다. 만약 새로운 필드가 추가된다면
// 더 이상 유닛 유사 구조체가 아닐 것이므로, 유닛 유사 구조체로서 생성된
// 동일 이름의 상수는 크레이트 외부에서 공개되지 않습니다.
// 이 코드는 컴파일에 실패합니다.
let token = Token;
// `Id` 인스턴스를 생성할 수 없습니다. 만약 새로운 필드가 추가된다면
// 생성자 함수의 시그니처가 변경될 것이므로, 생성자 함수가 크레이트 외부에서
// 공개되지 않습니다. 이 코드는 컴파일에 실패합니다.
let id = Id(5);
// `Error` 인스턴스는 생성할 수 있습니다. 새로운 변형이 도입되더라도
// 이 코드가 컴파일에 실패하는 결과로 이어지지는 않기 때문입니다.
let error = Error::Message("foo".to_string());
// `Message::Send` 또는 `Message::Reaction` 인스턴스를 생성할 수 없습니다.
// 만약 업스트림의 새로운 버전에서 새로운 필드가 추가된다면
// 컴파일에 실패할 것이므로, 이를 불허합니다.
let message = Message::Send { from: 0, to: 1, contents: "foo".to_string(), };
let message = Message::Reaction(0);
// Cannot construct an instance of `Message::Quit`; if this were converted to
// a tuple enum variant `upstream`, this would fail to compile.
let message = Message::Quit;
정의하는 크레이트 외부에서 비완전 타입을 매칭할 때 다음과 같은 제약 사항이 있습니다.
- When pattern matching on a non-exhaustive variant (
structorenumvariant), a StructPattern must be used which must include a... A tuple enum variant’s constructor’s visibility is reduced to be no greater thanpub(crate). - When pattern matching on a non-exhaustive
enum, matching on a variant does not contribute towards the exhaustiveness of the arms. The following examples of matching do not compile when outside the defining crate:
// 이들은 업스트림 크레이트에서 `#[non_exhaustive]` 가 주석 처리된 상태로 정의된 타입들입니다.
use upstream::{Config, Token, Id, Error, Message};
// 와일드카드 암(arm)을 포함하지 않고는 비완전 열거형을 매칭할 수 없습니다.
match error {
Error::Message(ref s) => { },
Error::Other => { },
// `_ => {},` 를 추가하면 컴파일됩니다.
}
// 와일드카드 없이 비완전 구조체를 매칭할 수 없습니다.
if let Ok(Config { window_width, window_height }) = config {
// `..` 을 추가하면 컴파일됩니다.
}
// 중괄호 구조체 구문과 와일드카드를 사용하지 않고는 비완전 유닛 유사 또는 튜플 구조체를 매칭할 수 없습니다.
// 이는 `let Token { .. } = token;` 으로 작성해야 컴파일됩니다.
let Token = token;
// 이는 `let Id { 0: id_number, .. } = id;` 로 작성해야 컴파일됩니다.
let Id(id_number) = id;
match message {
// 와일드카드를 포함하지 않고는 비완전 구조체형 열거형 변형을 매칭할 수 없습니다.
Message::Send { from, to, contents } => { },
// 비완전 튜플형 또는 유닛형 열거형 변형은 매칭할 수 없습니다.
Message::Reaction(type) => { },
Message::Quit => { },
}
또한 하나 이상의 비완전 변형을 포함하는 열거형에 대해서는 숫자 캐스팅(as)을 사용하는 것이 허용되지 않습니다.
예를 들어, 다음 열거형은 비완전 변형을 포함하지 않으므로 캐스팅할 수 있습니다.
#![allow(unused)]
fn main() {
#[non_exhaustive]
pub enum Example {
First,
Second
}
}
하지만 열거형이 단 하나의 비완전 변형이라도 포함하고 있다면, 캐스팅은 에러가 됩니다. 동일한 열거형의 수정된 버전을 고려해 보세요.
#![allow(unused)]
fn main() {
#[non_exhaustive]
pub enum EnumWithNonExhaustiveVariants {
First,
#[non_exhaustive]
Second
}
}
use othercrate::EnumWithNonExhaustiveVariants;
// 에러: 다른 크레이트에 정의된 비완전 변형을 가진 열거형은 캐스팅할 수 없습니다.
let _ = EnumWithNonExhaustiveVariants::First as u8;
비완전 타입은 다운스트림 크레이트에서 항상 거주하는(inhabited) 타입으로 간주됩니다.
디버거 속성
다음 속성들 은 GDB나 WinDbg와 같은 서드파티 디버거를 사용할 때 디버깅 경험을 향상시키기 위해 사용됩니다.
debugger_visualizer 속성
The debugger_visualizer attribute can be used to embed a debugger visualizer file into the debug information. This improves the debugger experience when displaying values.
Example
#![debugger_visualizer(natvis_file = "Example.natvis")] #![debugger_visualizer(gdb_script_file = "example.py")]
The debugger_visualizer attribute uses the MetaListNameValueStr syntax to specify its inputs. One of the following keys must be specified:
The debugger_visualizer attribute may only be applied to a module or to the crate root.
The debugger_visualizer attribute may be used any number of times on a form. All specified visualizer files will be loaded.
Natvis와 함께 debugger_visualizer 사용하기
Natvis는 타입 표시를 사용자 정의하기 위해 선언적 규칙을 사용하는 Microsoft 디버거(예: Visual Studio 및 WinDbg)를 위한 XML 기반 프레임워크입니다. Natvis 형식에 대한 자세한 내용은 Microsoft의 Natvis 문서 를 참조하세요.
이 속성은 -windows-msvc 타겟에서만 Natvis 파일 포함을 지원합니다.
The path to the Natvis file is specified with the natvis_file key, which is a path relative to the source file.
Example
#![debugger_visualizer(natvis_file = "Rectangle.natvis")] struct FancyRect { x: f32, y: f32, dx: f32, dy: f32, } fn main() { let fancy_rect = FancyRect { x: 10.0, y: 10.0, dx: 5.0, dy: 5.0 }; println!("set breakpoint here"); }
Rectangle.natviscontains:<?xml version="1.0" encoding="utf-8"?> <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <Type Name="foo::FancyRect"> <DisplayString>({x},{y}) + ({dx}, {dy})</DisplayString> <Expand> <Synthetic Name="LowerLeft"> <DisplayString>({x}, {y})</DisplayString> </Synthetic> <Synthetic Name="UpperLeft"> <DisplayString>({x}, {y + dy})</DisplayString> </Synthetic> <Synthetic Name="UpperRight"> <DisplayString>({x + dx}, {y + dy})</DisplayString> </Synthetic> <Synthetic Name="LowerRight"> <DisplayString>({x + dx}, {y})</DisplayString> </Synthetic> </Expand> </Type> </AutoVisualizer>WinDbg에서 볼 때,
fancy_rect변수는 다음과 같이 표시될 것입니다:> Variables: > fancy_rect: (10.0, 10.0) + (5.0, 5.0) > LowerLeft: (10.0, 10.0) > UpperLeft: (10.0, 15.0) > UpperRight: (15.0, 15.0) > LowerRight: (15.0, 10.0)
GDB와 함께 debugger_visualizer 사용하기
GDB는 타입이 디버거 뷰에서 시각화되는 방식을 설명하는 프리티 프린터(pretty printer) 라고 불리는 구조화된 파이썬 스크립트의 사용을 지원합니다. 프리티 프린터에 대한 자세한 내용은 GDB의 프리티 프린팅 문서 를 참조하세요.
Note
Embedded pretty printers are not automatically loaded when debugging a binary under GDB.
There are two ways to enable auto-loading embedded pretty printers:
- 자동 로드 안전 경로에 디렉터리나 바이너리를 명시적으로 추가하기 위해 추가 인수를 사용하여 GDB를 실행합니다:
gdb -iex "add-auto-load-safe-path safe-path path/to/binary" path/to/binary. 자세한 내용은 GDB의 자동 로드 문서 를 참조하세요.$HOME/.config/gdb아래에gdbinit이라는 파일을 생성합니다(디렉터리가 없으면 생성해야 할 수도 있습니다). 해당 파일에 다음 라인을 추가합니다:add-auto-load-safe-path path/to/binary.
These scripts are embedded using the gdb_script_file key, which is a path relative to the source file.
Example
#![debugger_visualizer(gdb_script_file = "printer.py")] struct Person { name: String, age: i32, } fn main() { let bob = Person { name: String::from("Bob"), age: 10 }; println!("set breakpoint here"); }
printer.pycontains:import gdb class PersonPrinter: "Person 출력" def __init__(self, val): self.val = val self.name = val["이름"] self.age = int(val["나이"]) def to_string(self): return "{}는 {}살입니다.".format(self.name, self.age) def lookup(val): lookup_tag = val.type.tag if lookup_tag is None: return None if "foo::Person" == lookup_tag: return PersonPrinter(val) return None gdb.current_objfile().pretty_printers.append(lookup)크레이트의 디버그 실행 파일이 GDB1 로 전달되면,
print bob은 다음과 같이 표시될 것입니다:"Bob" is 10 years old.
collapse_debuginfo 속성
The collapse_debuginfo attribute controls whether code locations from a macro definition are collapsed into a single location associated with the macro’s call site when generating debuginfo for code calling this macro.
Example
#![allow(unused)] fn main() { #[collapse_debuginfo(yes)] macro_rules! example { () => { println!("hello!"); }; } }When using a debugger, invoking the
examplemacro may appear as though it is calling a function. That is, when you step to the invocation site, it may show the macro invocation rather than the expanded code.
The syntax for the collapse_debuginfo attribute is:
Syntax
CollapseDebuginfoAttribute → collapse_debuginfo ( CollapseDebuginfoOption )
CollapseDebuginfoOption →
yes
| no
| external
The collapse_debuginfo attribute may only be applied to a macro_rules definition.
The collapse_debuginfo attribute may used only once on a macro.
The collapse_debuginfo attribute accepts these options:
#[collapse_debuginfo(yes)]— Code locations in debuginfo are collapsed.#[collapse_debuginfo(no)]— Code locations in debuginfo are not collapsed.#[collapse_debuginfo(external)]— Code locations in debuginfo are collapsed only if the macro comes from a different crate.
The external behavior is the default for macros that don’t have this attribute unless they are built-in macros. For built-in macros the default is yes.
Note
rustchas a-C collapse-macro-debuginfoCLI option to override both the default behavior and the values of any#[collapse_debuginfo]attributes.
-
참고: 이는
String과 같은 표준 라이브러리 타입에 대해 프리티 프린터를 구성하는rust-gdb스크립트를 사용하고 있다고 가정합니다. ↩
구문과 표현식
Rust는 주로 표현식(expression) 언어입니다. 이는 대부분의 값을 생성하거나 부수 효과를 일으키는 평가가 표현식 이라는 통일된 구문 범주에 의해 유도됨을 의미합니다. 각 종류의 표현식은 일반적으로 다른 종류의 표현식 내에 중첩 될 수 있으며, 표현식의 평가 규칙에는 표현식에 의해 생성되는 값과 그 하위 표현식들이 평가되는 순서를 모두 명시하는 것이 포함됩니다.
반면, 구문(statement)은 주로 표현식 평가를 포함하고 명시적으로 순서를 지정하는 역할을 합니다.
구문
Syntax
Statement →
;
| Item
| LetStatement
| ExpressionStatement
| OuterAttribute* MacroInvocationSemi
구문(statement) 은 블록 의 구성 요소이며, 블록은 다시 외부 표현식 이나 함수 의 구성 요소가 됩니다.
Rust에는 두 가지 종류의 구문이 있습니다: 선언 구문 과 표현식 구문.
선언 구문
선언 구문(declaration statement) 은 둘러싼 구문 블록에 하나 이상의 이름 을 도입하는 구문입니다. 선언된 이름은 새로운 변수나 새로운 아이템 을 나타낼 수 있습니다.
선언 구문의 두 가지 종류는 아이템 선언과 let 구문입니다.
아이템 선언
아이템 선언 구문 은 모듈 내의 아이템 선언 과 동일한 구문 형식을 가집니다.
구문 블록 내에서 아이템을 선언하면 해당 아이템의 스코프(scope) 는 해당 구문을 포함하는 블록으로 제한됩니다. 아이템에는 정규 경로(canonical path) 가 주어지지 않으며, 해당 아이템이 선언할 수 있는 하위 아이템들도 마찬가지입니다.
이에 대한 예외로, 구현(implementations) 에 의해 정의된 연관 아이템들은 해당 아이템과 (해당되는 경우) 트레잇에 접근 가능한 한 외부 스코프에서도 여전히 접근 가능합니다. 그 외의 경우에는 모듈 내부에 아이템을 선언하는 것과 의미상 동일합니다.
둘러싼 함수의 제네릭 매개변수, 매개변수, 지역 변수들을 암시적으로 캡처하지 않습니다. 예를 들어, inner 는 outer_var 에 접근할 수 없습니다.
#![allow(unused)]
fn main() {
fn outer() {
let outer_var = true;
fn inner() { /* 여기서 outer_var는 스코프에 없습니다 */ }
inner();
}
}
let 구문
Syntax
LetStatement →
OuterAttribute* let PatternNoTopAlt ( : Type )?
(
= Expression
| = Expressionexcept LazyBooleanExpression or end with a } else BlockExpression
)? ;
let 구문 은 패턴 에 의해 주어지는 새로운 변수 집합을 도입합니다. 패턴 뒤에는 선택적으로 타입 주석이 올 수 있으며, 그 후 구문이 끝나거나 초기화 표현식과 선택적인 else 블록이 뒤따릅니다.
타입 주석이 주어지지 않으면 컴파일러가 타입을 추론하며, 확정적인 추론을 위한 타입 정보가 부족하면 에러를 발생시킵니다.
변수 선언에 의해 도입된 모든 변수들은 선언 지점부터 해당 블록 스코프가 끝날 때까지 가시성을 가집니다. 단, 다른 변수 선언에 의해 가려지는(shadowed) 경우는 제외합니다.
else 블록이 없는 경우 패턴은 반박 불가능(irrefutable)해야 합니다. else 블록이 있는 경우 패턴은 반박 가능(refutable)할 수 있습니다.
패턴이 일치하지 않으면(else 가 있으므로 반박 가능한 패턴이어야 함) else 블록이 실행됩니다. else 블록은 반드시 발산(diverge)해야 합니다 (즉, never 타입 으로 평가되어야 함).
#![allow(unused)]
fn main() {
let (mut v, w) = (vec![1, 2, 3], 42); // 바인딩은 mut 또는 const일 수 있습니다
let Some(t) = v.pop() else { // 반박 가능한 패턴은 else 블록을 요구합니다
panic!(); // else 블록은 반드시 발산해야 합니다
};
let [u, v] = [v[0], v[1]] else { // 이 패턴은 반박 불가능하므로, 컴파일러는 else 블록이
// 불필요하다는 린트를 발생시킬 것입니다.
panic!();
};
}
표현식 구문
Syntax
ExpressionStatement →
ExpressionWithoutBlock ;
| ExpressionWithBlock ;?
표현식 구문(expression statement) 은 표현식 을 평가하고 그 결과를 무시하는 구문입니다. 일반적으로 표현식 구문의 목적은 표현식 평가에 따른 부수 효과를 일으키는 것입니다.
블록 표현식이나 제어 흐름 표현식으로만 구성된 표현식이 구문이 허용되는 컨텍스트에서 사용되는 경우, 뒤따르는 세미콜론을 생략할 수 있습니다. 이는 독립적인 구문으로 파싱되는 것과 다른 표현식의 일부로 파싱되는 것 사이의 모호함을 유발할 수 있는데, 이 경우 구문으로 파싱됩니다.
The type of ExpressionWithBlock expressions when used as statements must be the unit type.
#![allow(unused)]
fn main() {
let mut v = vec![1, 2, 3];
v.pop(); // pop에서 반환된 요소를 무시합니다
if v.is_empty() {
v.push(5);
} else {
v.remove(0);
} // 세미콜론은 생략될 수 있습니다.
[1]; // 별개의 표현식 구문이며, 인덱싱 표현식이 아닙니다.
}
마지막 세미콜론이 생략된 경우, 결과는 반드시 () 타입이어야 합니다.
#![allow(unused)]
fn main() {
// 나쁨: 블록의 타입이 ()가 아니라 i32임
// 에러: 기본 반환 타입 때문에 `()` 가 기대됨
// if true {
// 1
// }
// 좋음: 블록의 타입이 i32임
if true {
1
} else {
2
};
}
Attributes on statements
구문은 외부 속성 을 허용합니다. 구문에서 의미를 갖는 속성은 cfg 와 린트 체크 속성 입니다.
표현식
Syntax
Expression →
ExpressionWithoutBlock
| ExpressionWithBlock
ExpressionWithoutBlock →
OuterAttribute* ExpressionWithoutBlockNoAttrs
ExpressionWithoutBlockNoAttrs →
LiteralExpression
| PathExpression
| OperatorExpression
| GroupedExpression
| ArrayExpression
| AwaitExpression
| IndexExpression
| TupleExpression
| TupleIndexingExpression
| StructExpression
| CallExpression
| MethodCallExpression
| FieldExpression
| ClosureExpression
| AsyncBlockExpression
| ContinueExpression
| BreakExpression
| RangeExpression
| ReturnExpression
| UnderscoreExpression
| MacroInvocation
ExpressionWithBlock →
OuterAttribute* ExpressionWithBlockNoAttrs
ExpressionWithBlockNoAttrs →
BlockExpression
| ConstBlockExpression
| UnsafeBlockExpression
| LoopExpression
| IfExpression
| MatchExpression
표현식은 두 가지 역할을 가질 수 있습니다: 항상 값(value) 을 생성하며, 효과(effects) (또는 “부수 효과”)를 가질 수 있습니다.
표현식은 값으로 평가(evaluates to) 되며, 평가 중에 효과를 가집니다.
많은 표현식은 하위 표현식을 포함하며, 이를 표현식의 피연산자(operands) 라고 부릅니다.
각 종류의 표현식의 의미는 몇 가지 사항을 결정합니다:
- 표현식을 평가할 때 피연산자를 평가할지 여부
- 피연산자를 평가하는 순서
- 표현식의 값을 얻기 위해 피연산자들의 값을 결합하는 방법
이러한 방식으로 표현식의 구조는 실행 구조를 결정합니다. 블록은 단지 또 다른 종류의 표현식일 뿐이므로, 블록, 구문, 표현식 그리고 다시 블록이 임의의 깊이로 서로 재귀적으로 중첩될 수 있습니다.
Note
We give names to the operands of expressions so that we may discuss them, but these names are not stable and may be changed.
표현식 우선순위
Rust 연산자와 표현식의 우선순위는 다음과 같으며, 강한 것부터 약한 것 순으로 나열되어 있습니다. 동일한 우선순위 수준의 이항 연산자들은 그들의 결합성(associativity)에 따라 그룹화됩니다.
| 연산자/표현식 | 결합성 |
|---|---|
| Paths | |
| Method calls | |
| Field expressions | 왼쪽에서 오른쪽으로 |
| Function calls, array indexing | |
? | |
Unary - ! * borrow | |
as | 왼쪽에서 오른쪽으로 |
* / % | 왼쪽에서 오른쪽으로 |
+ - | 왼쪽에서 오른쪽으로 |
<< >> | 왼쪽에서 오른쪽으로 |
& | 왼쪽에서 오른쪽으로 |
^ | 왼쪽에서 오른쪽으로 |
| | 왼쪽에서 오른쪽으로 |
== != < > <= >= | 괄호 필요 |
&& | 왼쪽에서 오른쪽으로 |
|| | 왼쪽에서 오른쪽으로 |
.. ..= | 괄호 필요 |
= += -= *= /= %= &= |= ^= <<= >>= | 오른쪽에서 왼쪽으로 |
return break closures |
피연산자의 평가 순서
다음 목록의 표현식들은 모두 목록 뒤에 설명된 것과 동일한 방식으로 피연산자를 평가합니다. 다른 표현식들은 피연산자를 취하지 않거나, 각각의 페이지에 설명된 대로 조건부로 평가합니다.
- 역참조 표현식
- 에러 전파 표현식
- 부정 표현식
- 산술 및 논리 이항 연산자
- 비교 연산자
- 타입 캐스트 표현식
- 그룹화된 표현식
- 배열 표현식
- Await 표현식
- 인덱스 표현식
- 튜플 표현식
- 튜플 인덱스 표현식
- 구조체 표현식
- 호출 표현식
- 메서드 호출 표현식
- 필드 표현식
- Break 표현식
- 범위 표현식
- Return 표현식
이러한 표현식의 피연산자들은 표현식의 효과가 적용되기 전에 평가됩니다. 여러 피연산자를 취하는 표현식은 소스 코드에 작성된 대로 왼쪽에서 오른쪽으로 평가됩니다.
Note
Which subexpressions are the operands of an expression is determined by expression precedence as per the previous section.
예를 들어, 두 개의 next 메서드 호출은 항상 동일한 순서로 호출됩니다:
#![allow(unused)]
fn main() {
// 참조를 피하기 위해 배열 대신 vec를 사용합니다.
// 이 예제가 작성된 시점에는 안정적인 소유권 있는(owned) 배열 반복자가
// 없었기 때문입니다.
let mut one_two = vec![1, 2].into_iter();
assert_eq!(
(1, 2),
(one_two.next().unwrap(), one_two.next().unwrap())
);
}
Note
Since this is applied recursively, these expressions are also evaluated from innermost to outermost, ignoring siblings until there are no inner subexpressions.
Place expressions and value expressions
표현식은 두 가지 주요 범주로 나뉩니다: 장소 표현식(place expressions)과 값 표현식(value expressions). 또한 피할당자 표현식(assignee expressions)이라고 불리는 세 번째 보조 범주도 있습니다. 각 표현식 내에서 피연산자들은 마찬가지로 장소 컨텍스트나 값 컨텍스트 중 하나에서 나타날 수 있습니다. 표현식의 평가는 표현식 자체의 범주와 표현식이 나타나는 컨텍스트 모두에 달려 있습니다.
장소 표현식(place expression) 은 메모리 위치를 나타내는 표현식입니다.
이러한 표현식들은 지역 변수, 정적 변수 를 참조하는 경로, 역참조 (*expr), 배열 인덱싱 표현식 (expr[expr]), 필드 참조 (expr.f) 및 괄호로 둘러싸인 장소 표현식입니다.
다른 모든 표현식은 값 표현식입니다.
값 표현식(value expression) 은 실제 값을 나타내는 표현식입니다.
다음 컨텍스트들은 장소 표현식 컨텍스트입니다:
- 복합 할당(compound assignment) 표현식의 왼쪽 피연산자.
- 단항 차용(borrow), 원시 차용(raw borrow) 또는 역참조(dereference) 연산자의 피연산자.
- 필드 표현식의 피연산자.
- 배열 인덱싱 표현식의 인덱스된 피연산자.
- 임의의 암시적 차용(implicit borrow) 의 피연산자.
- let 문 의 초기화 식(initializer).
- The scrutinee of an
if let,match, orwhile letexpression. - 함수형 업데이트(functional update) 구조체 표현식의 베이스(base).
Note
Historically, place expressions were called lvalues and value expressions were called rvalues.
피할당자 표현식(assignee expression) 은 할당(assignment) 표현식의 왼쪽 피연산자로 나타나는 표현식입니다. 명시적으로, 피할당자 표현식은 다음과 같습니다:
- 장소 표현식.
- 밑줄(Underscores).
- 피할당자 표현식의 튜플.
- Slices of assignee expressions.
- Tuple structs of assignee expressions.
- Structs of assignee expressions (with optionally named fields).
- Unit structs
피할당자 표현식 내에서는 임의의 괄호 사용이 허용됩니다.
이동 및 복사 타입
장소 표현식이 값 표현식 컨텍스트에서 평가되거나 패턴에서 값으로 바인딩될 때, 이는 해당 메모리 위치 에 저장된 값을 나타냅니다.
해당 값의 타입이 Copy 를 구현한다면, 값이 복사됩니다.
그 외의 상황에서 해당 타입이 Sized 라면, 값을 이동(move)시키는 것이 가능할 수 있습니다.
오직 다음 장소 표현식들로부터만 값을 이동시킬 수 있습니다:
- 현재 차용(borrow)되지 않은 변수.
- 임시 값.
- 값을 이동시킬 수 있는 장소 표현식의 필드 들 중
Drop을 구현하지 않은 것. Box<T>타입을 가진 표현식을 역참조 한 결과로서, 마찬가지로 값을 이동시킬 수 있는 경우.
지역 변수로 평가되는 장소 표현식에서 값을 이동시킨 후에는 해당 위치가 초기화 해제(deinitialized)되며, 다시 초기화될 때까지 다시 읽을 수 없습니다.
그 외의 모든 경우, 값 표현식 컨텍스트에서 장소 표현식을 사용하려고 시도하는 것은 에러입니다.
가변성(Mutability)
장소 표현식에 할당(assigned) 하거나, 가변적으로 차용(borrowed) 하거나, 암시적으로 가변 차용 하거나, ref mut 를 포함하는 패턴에 바인딩하려면 해당 표현식은 반드시 가변(mutable) 이어야 합니다. 이를 가변 장소 표현식(mutable place expressions) 이라고 부릅니다. 반면에 다른 장소 표현식들은 불변 장소 표현식(immutable place expressions) 이라고 부릅니다.
다음 표현식들은 가변 장소 표현식 컨텍스트가 될 수 있습니다:
- 현재 차용되지 않은 가변 변수(variables).
- 가변
static아이템. - 임시 값.
- 필드(Fields): 이는 하위 표현식을 가변 장소 표현식 컨텍스트에서 평가합니다.
*mut T포인터의 역참조(Dereferences).&mut T타입을 가진 변수 또는 변수 필드의 역참조. 참고: 이는 다음 규칙의 요구 사항에 대한 예외입니다.DerefMut를 구현하는 타입의 역참조: 이는 역참조되는 값이 가변 장소 표현식 컨텍스트에서 평가될 것을 요구합니다.IndexMut를 구현하는 타입의 배열 인덱싱(Array indexing): 이는 인덱싱되는 값을 가변 장소 표현식 컨텍스트에서 평가하지만, 인덱스는 그렇지 않습니다.
임시 값(Temporaries)
대부분의 장소 표현식 컨텍스트에서 값 표현식을 사용할 때, 이름 없는 임시 메모리 위치가 생성되고 해당 값으로 초기화됩니다. 표현식은 static 으로 승격(promoted) 되지 않는 한 해당 위치로 평가됩니다. 임시 값의 드롭 스코프(drop scope) 는 보통 이를 둘러싼 구문의 끝입니다.
Super macros
Certain built-in macros may create temporaries whose scopes may be extended. These temporaries are super temporaries and these macros are super macros. Invocations of these macros are super macro call expressions. Arguments to these macros may be super operands.
Note
When a super macro call expression is an extending expression, its super operands are extending expressions and the scopes of the super temporaries are extended. See destructors.scope.lifetime-extension.exprs.
format_args!
Except for the format string argument, all arguments passed to format_args! are super operands.
#![allow(unused)]
fn main() {
fn temp() -> String { String::from("") }
// Due to the call being an extending expression and the argument
// being a super operand, the inner block is an extending expression,
// so the scope of the temporary created in its trailing expression
// is extended.
let _ = format_args!("{}", { &temp() }); // OK
}
The super operands of format_args! are implicitly borrowed and are therefore place expression contexts. When a value expression is passed as an argument, it creates a super temporary.
#![allow(unused)]
fn main() {
fn temp() -> String { String::from("") }
let x = format_args!("{}", temp());
x; // <-- The temporary is extended, allowing use here.
}
The expansion of a call to format_args! sometimes creates other internal super temporaries.
#![allow(unused)]
fn main() {
let x = {
// This call creates an internal temporary.
let x = format_args!("{:?}", 0);
x // <-- The temporary is extended, allowing its use here.
}; // <-- The temporary is dropped here.
x; // 오류
}
#![allow(unused)]
fn main() {
// This call doesn't create an internal temporary.
let x = { let x = format_args!("{}", 0); x };
x; // OK
}
Note
The details of when
format_args!does or does not create internal temporaries are currently unspecified.
pin!
The argument to pin! is a super operand.
#![allow(unused)]
fn main() {
use core::pin::pin;
fn temp() {}
// As above for `format_args!`.
let _ = pin!({ &temp() }); // OK
}
The argument to pin! is a value expression context and creates a super temporary.
#![allow(unused)]
fn main() {
use core::pin::pin;
fn temp() {}
// The argument is evaluated into a super temporary.
let x = pin!(temp());
// The temporary is extended, allowing its use here.
x; // OK
}
Implicit borrows
특정 표현식들은 표현식을 암시적으로 차용함으로써 장소 표현식으로 취급합니다. 예를 들어, == 연산자는 피연산자를 암시적으로 차용하기 때문에 두 개의 크기 미지정 슬라이스(slices) 의 동등성을 직접 비교하는 것이 가능합니다.
#![allow(unused)]
fn main() {
let c = [1, 2, 3];
let d = vec![1, 2, 3];
let a: &[i32];
let b: &[i32];
a = &c;
b = &d;
// ...
*a == *b;
// 동등한 형태:
::std::cmp::PartialEq::eq(&*a, &*b);
}
암시적 차용은 다음 표현식들에서 발생할 수 있습니다:
- 메서드 호출(method-call) 표현식의 왼쪽 피연산자.
- 필드(field) 표현식의 왼쪽 피연산자.
- 호출 표현식(call expressions) 의 왼쪽 피연산자.
- 배열 인덱싱(array indexing) 표현식의 왼쪽 피연산자.
- 역참조 연산자(dereference operator) (
*)의 피연산자. - 비교(comparison) 의 피연산자들.
- 복합 할당(compound assignment) 의 왼쪽 피연산자들.
- Arguments to
format_args!except the format string.
Overloading traits
다음 중 많은 연산자와 표현식들은 std::ops 나 std::cmp 에 있는 트레잇들을 사용하여 다른 타입에 대해 오버로드될 수 있습니다. 이러한 트레잇들은 core::ops 및 core::cmp 에도 동일한 이름으로 존재합니다.
Expression attributes
표현식 앞의 외부 속성(Outer attributes) 은 오직 다음 몇 가지 구체적인 경우에만 허용됩니다:
- 구문(statement) 으로 사용된 표현식 앞에 올 때.
- 배열 표현식(array expressions), 튜플 표현식(tuple expressions), 호출 표현식(call expressions), 그리고 튜플 스타일 구조체(struct) 표현식의 요소들에 올 때.
- 블록 표현식(block expressions) 의 마지막 표현식(tail expression)에 올 때.
다음의 경우에는 절대 허용되지 않습니다:
- 범위(Range) 표현식 앞.
- Binary operator expressions (ArithmeticOrLogicalExpression, ComparisonExpression, LazyBooleanExpression, TypeCastExpression, AssignmentExpression, CompoundAssignmentExpression).
리터럴 표현식
Syntax
LiteralExpression →
CHAR_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| C_STRING_LITERAL
| RAW_C_STRING_LITERAL
| INTEGER_LITERAL
| FLOAT_LITERAL
| true
| false
리터럴 표현식(literal expression) 은 단일 토큰으로 구성된 표현식으로, 이름을 통해 참조하거나 다른 평가 규칙을 따르는 대신, 그것이 평가되는 값을 즉시 그리고 직접적으로 나타냅니다.
리터럴은 상수 표현식 의 한 형태이므로, (주로) 컴파일 타임에 평가됩니다.
앞서 설명한 어휘적 리터럴 형식들은 각각 리터럴 표현식을 구성할 수 있으며, true 와 false 키워드도 마찬가지입니다.
#![allow(unused)]
fn main() {
"hello"; // 문자열 타입
'5'; // 문자 타입
5; // 정수 타입
}
아래 설명에서 토큰의 문자열 표현(string representation) 은 렉서 문법 스니펫의 토큰 생성물과 일치하는 입력 문자 시퀀스를 의미합니다.
Note
This string representation never includes a character
U+000D(CR) immediately followed byU+000A(LF): this pair would have been previously transformed into a singleU+000A(LF).
이스케이프
아래의 텍스트 리터럴 표현식에 대한 설명에서는 여러 형태의 이스케이프(escape) 를 사용합니다.
각 이스케이프 형태는 다음으로 특징지어집니다:
- 이스케이프 시퀀스(escape sequence): 항상
U+005C(\)로 시작하는 문자 시퀀스 - 이스케이프된 값(escaped value): 단일 문자 또는 빈 문자 시퀀스
아래의 이스케이프 정의에서:
- 8진수 숫자(octal digit) 는
0-7범위의 문자 중 하나입니다. - 16진수 숫자(hexadecimal digit) 는
0-9,a-f, 또는A-F범위의 문자 중 하나입니다.
단순 이스케이프
다음 표의 첫 번째 열에 나타나는 각 문자 시퀀스는 이스케이프 시퀀스입니다.
각 경우에, 이스케이프된 값은 두 번째 열의 해당 항목에 주어진 문자입니다.
| 이스케이프 시퀀스 | 이스케이프된 값 |
|---|---|
\0 | U+0000 (NUL) |
\t | U+0009 (HT) |
\n | U+000A (LF) |
\r | U+000D (CR) |
| 큰따옴표 | U+0022 (큰따옴표) |
\' | U+0027 (작은따옴표) |
\\ | U+005C (역슬래시) |
8비트 이스케이프
이스케이프 시퀀스는 \x 뒤에 두 개의 16진수 숫자가 오는 것으로 구성됩니다.
이스케이프된 값은 이스케이프 시퀀스의 마지막 두 문자를 16진수 정수로 해석한 결과(기수가 16인 u8::from_str_radix 를 사용한 것과 같음)를 유니코드 스칼라 값(Unicode scalar value) 으로 가지는 문자입니다.
Note
The escaped value therefore has a Unicode scalar value in the range of
u8.
7비트 이스케이프
이스케이프 시퀀스는 \x 뒤에 8진수 숫자 하나와 16진수 숫자 하나가 차례로 오는 것으로 구성됩니다.
이스케이프된 값은 이스케이프 시퀀스의 마지막 두 문자를 16진수 정수로 해석한 결과(기수가 16인 u8::from_str_radix 를 사용한 것과 같음)를 유니코드 스칼라 값(Unicode scalar value) 으로 가지는 문자입니다.
유니코드 이스케이프
이스케이프 시퀀스는 \u{ 뒤에 16진수 숫자 또는 _ 로 구성된 문자 시퀀스가 오고, 그 뒤에 } 가 오는 것으로 구성됩니다.
이스케이프된 값은 이스케이프 시퀀스에 포함된 16진수 숫자들을 16진수 정수로 해석한 결과(기수가 16인 u32::from_str_radix 를 사용한 것과 같음)를 유니코드 스칼라 값 으로 가지는 문자입니다.
Note
The permitted forms of a CHAR_LITERAL or STRING_LITERAL token ensure that there is such a character.
문자열 연속 이스케이프
이스케이프 시퀀스는 \ 뒤에 즉시 U+000A (LF)가 오고, 그다음 비-공백 문자 전까지의 모든 공백 문자들로 구성됩니다. 이 목적을 위해 공백 문자는 U+0009 (HT), U+000A (LF), U+000D (CR), 그리고 U+0020 (SPACE)입니다.
이스케이프된 값은 빈 문자 시퀀스입니다.
Note
The effect of this form of escape is that a string continuation skips following whitespace, including additional newlines. Thus
a,bandcare equal:#![allow(unused)] fn main() { let a = "foobar"; let b = "foo\ bar"; let c = "foo\ bar"; assert_eq!(a, b); assert_eq!(b, c); }추가적인 줄 바꿈을 건너뛰는 것(예시 c와 같은 경우)은 잠재적으로 혼란스럽고 예상치 못한 일일 수 있습니다. 이 동작은 미래에 조정될 수 있습니다. 결정이 내려지기 전까지는 라인 연속 기능을 사용하여 여러 줄 바꿈을 건너뛰는 것에 의존하지 않는 것이 권장됩니다. 자세한 내용은 이 이슈 를 참조하세요.
문자 리터럴 표현식
A character literal expression consists of a single CHAR_LITERAL token.
The expression’s type is the primitive char type.
토큰은 접미사(suffix)를 가져서는 안 됩니다.
토큰의 리터럴 내용(literal content) 은 토큰의 문자열 표현에서 첫 번째 U+0027 (') 뒤에 오고 마지막 U+0027 (') 앞에 오는 문자 시퀀스입니다.
리터럴 표현식의 표현된 문자(represented character) 는 다음과 같이 리터럴 내용으로부터 유도됩니다:
- 리터럴 내용이 다음 형태의 이스케이프 시퀀스 중 하나인 경우, 표현된 문자는 해당 이스케이프 시퀀스의 이스케이프된 값입니다.
- 그렇지 않은 경우, 표현된 문자는 리터럴 내용을 구성하는 단일 문자입니다.
The expression’s value is the char corresponding to the represented character’s Unicode scalar value.
Note
The permitted forms of a CHAR_LITERAL token ensure that these rules always produce a single character.
문자 리터럴 표현식의 예시:
#![allow(unused)]
fn main() {
'R'; // R
'\''; // '
'\x52'; // R
'\u{00E6}'; // 라틴어 소문자 AE (U+00E6)
}
문자열 리터럴 표현식
A string literal expression consists of a single STRING_LITERAL or RAW_STRING_LITERAL token.
The expression’s type is a shared reference (with static lifetime) to the primitive str type. That is, the type is &'static str.
토큰은 접미사(suffix)를 가져서는 안 됩니다.
토큰의 리터럴 내용(literal content) 은 토큰의 문자열 표현에서 첫 번째 U+0022 (") 뒤에 오고 마지막 U+0022 (") 앞에 오는 문자 시퀀스입니다.
리터럴 표현식의 표현된 문자열(represented string) 은 다음과 같이 리터럴 내용으로부터 유도된 문자 시퀀스입니다:
-
If the token is a STRING_LITERAL, each escape sequence of any of the following forms occurring in the literal content is replaced by the escape sequence’s escaped value.
- 단순 이스케이프
- 7비트 이스케이프
- 유니코드 이스케이프
- 문자열 연속 이스케이프
이러한 대체는 왼쪽에서 오른쪽 순서로 발생합니다. 예를 들어,
"\\x41"토큰은 문자\,x,4,1로 변환됩니다.
- If the token is a RAW_STRING_LITERAL, the represented string is identical to the literal content.
The expression’s value is a reference to a statically allocated str containing the UTF-8 encoding of the represented string.
문자열 리터럴 표현식의 예시:
#![allow(unused)]
fn main() {
"foo"; r"foo"; // foo
"\"foo\""; r#""foo""#; // "foo"
"foo #\"# bar";
r##"foo #"# bar"##; // foo #"# bar
"\x52"; "R"; r"R"; // R
"\\x52"; r"\x52"; // \x52
}
바이트 리터럴 표현식
A byte literal expression consists of a single BYTE_LITERAL token.
표현식의 타입은 기본 u8 타입입니다.
토큰은 접미사(suffix)를 가져서는 안 됩니다.
토큰의 리터럴 내용(literal content) 은 토큰의 문자열 표현에서 첫 번째 U+0027 (') 뒤에 오고 마지막 U+0027 (') 앞에 오는 문자 시퀀스입니다.
리터럴 표현식의 표현된 문자(represented character) 는 다음과 같이 리터럴 내용으로부터 유도됩니다:
- 그렇지 않은 경우, 표현된 문자는 리터럴 내용을 구성하는 단일 문자입니다.
표현식의 값은 표현된 문자의 유니코드 스칼라 값 입니다.
Note
The permitted forms of a BYTE_LITERAL token ensure that these rules always produce a single character, whose Unicode scalar value is in the range of
u8.
바이트 리터럴 표현식의 예시:
#![allow(unused)]
fn main() {
b'R'; // 82
b'\''; // 39
b'\x52'; // 82
b'\xA0'; // 160
}
바이트 문자열 리터럴 표현식
A byte string literal expression consists of a single BYTE_STRING_LITERAL or RAW_BYTE_STRING_LITERAL token.
표현식의 타입은 요소 타입이 u8 인 배열에 대한 공유 참조(static 라이프타임을 가짐)입니다. 즉, 타입은 &'static [u8; N] 이며, 여기서 N 은 아래에서 설명하는 표현된 문자열의 바이트 수입니다.
토큰은 접미사(suffix)를 가져서는 안 됩니다.
토큰의 리터럴 내용(literal content) 은 토큰의 문자열 표현에서 첫 번째 U+0022 (") 뒤에 오고 마지막 U+0022 (") 앞에 오는 문자 시퀀스입니다.
리터럴 표현식의 표현된 문자열(represented string) 은 다음과 같이 리터럴 내용으로부터 유도된 문자 시퀀스입니다:
-
If the token is a BYTE_STRING_LITERAL, each escape sequence of any of the following forms occurring in the literal content is replaced by the escape sequence’s escaped value.
- 단순 이스케이프
- 8비트 이스케이프
- 문자열 연속 이스케이프
이러한 대체는 왼쪽에서 오른쪽 순서로 발생합니다. 예를 들어,
b"\\x41"토큰은 문자\,x,4,1로 변환됩니다.
- If the token is a RAW_BYTE_STRING_LITERAL, the represented string is identical to the literal content.
표현식의 값은 표현된 문자열 속 문자들의 유니코드 스칼라 값 들을 동일한 순서로 포함하는 정적으로 할당된 배열에 대한 참조입니다.
Note
The permitted forms of BYTE_STRING_LITERAL and RAW_BYTE_STRING_LITERAL tokens ensure that these rules always produce array element values in the range of
u8.
바이트 문자열 리터럴 표현식의 예시:
#![allow(unused)]
fn main() {
b"foo"; br"foo"; // foo
b"\"foo\""; br#""foo""#; // "foo"
b"foo #\"# bar";
br##"foo #"# bar"##; // foo #"# bar
b"\x52"; b"R"; br"R"; // R
b"\\x52"; br"\x52"; // \x52
}
C 문자열 리터럴 표현식
A C string literal expression consists of a single C_STRING_LITERAL or RAW_C_STRING_LITERAL token.
표현식의 타입은 표준 라이브러리 CStr 타입에 대한 공유 참조(static 라이프타임을 가짐)입니다. 즉, 타입은 &'static core::ffi::CStr 입니다.
토큰은 접미사(suffix)를 가져서는 안 됩니다.
토큰의 리터럴 내용(literal content) 은 토큰의 문자열 표현에서 첫 번째 U+0022 (") 뒤에 오고 마지막 U+0022 (") 앞에 오는 문자 시퀀스입니다.
리터럴 표현식의 표현된 바이트(represented bytes) 는 다음과 같이 리터럴 내용으로부터 유도된 바이트 시퀀스입니다:
- If the token is a C_STRING_LITERAL, the literal content is treated as a sequence of items, each of which is either a single Unicode character other than
\or an escape. The sequence of items is converted to a sequence of bytes as follows:- 각 단일 유니코드 문자는 자신의 UTF-8 표현을 기여합니다.
- 각 단순 이스케이프 는 이스케이프된 값의 유니코드 스칼라 값 을 기여합니다.
- 각 8비트 이스케이프 는 이스케이프된 값의 유니코드 스칼라 값 을 포함하는 단일 바이트를 기여합니다.
- 각 유니코드 이스케이프 는 이스케이프된 값의 UTF-8 표현을 기여합니다.
- 각 문자열 연속 이스케이프 는 어떠한 바이트도 기여하지 않습니다.
- If the token is a RAW_C_STRING_LITERAL, the represented bytes are the UTF-8 encoding of the literal content.
Note
The permitted forms of C_STRING_LITERAL and RAW_C_STRING_LITERAL tokens ensure that the represented bytes never include a null byte.
표현식의 값은 표현된 바이트 뒤에 널 바이트가 뒤따르는 바이트 배열을 포함하는 정적으로 할당된 CStr 에 대한 참조입니다.
C 문자열 리터럴 표현식의 예시:
#![allow(unused)]
fn main() {
c"foo"; cr"foo"; // foo
c"\"foo\""; cr#""foo""#; // "foo"
c"foo #\"# bar";
cr##"foo #"# bar"##; // foo #"# bar
c"\x52"; c"R"; cr"R"; // R
c"\\x52"; cr"\x52"; // \x52
c"æ"; // 라틴어 소문자 AE (U+00E6)
c"\u{00E6}"; // 라틴어 소문자 AE (U+00E6)
c"\xC3\xA6"; // 라틴어 소문자 AE (U+00E6)
c"\xE6".to_bytes(); // [230]
c"\u{00E6}".to_bytes(); // [195, 166]
}
정수 리터럴 표현식
An integer literal expression consists of a single INTEGER_LITERAL token.
토큰에 접미사(suffix) 가 있는 경우, 접미사는 기본 정수 타입 중 하나인 u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, 또는 isize 여야 하며, 표현식은 해당 타입을 가집니다.
토큰에 접미사가 없는 경우, 표현식의 타입은 타입 추론에 의해 결정됩니다.
- 주변 프로그램 문맥으로부터 정수 타입을 유일하게 결정할 수 있다면, 표현식은 해당 타입을 가집니다.
- 프로그램 문맥이 타입을 충분히 제약하지 않는 경우, 기본값으로 부호 있는 32비트 정수인
i32가 사용됩니다.
- 프로그램 문맥이 타입을 과도하게 제약하는 경우, 정적 타입 에러로 간주됩니다.
정수 리터럴 표현식의 예시:
#![allow(unused)]
fn main() {
123; // i32 타입
123i32; // i32 타입
123u32; // u32 타입
123_u32; // u32 타입
let a: u64 = 123; // u64 타입
0xff; // i32 타입
0xff_u8; // u8 타입
0o70; // i32 타입
0o70_i16; // i16 타입
0b1111_1111_1001_0000; // i32 타입
0b1111_1111_1001_0000i64; // i64 타입
0usize; // usize 타입
}
표현식의 값은 다음과 같이 토큰의 문자열 표현으로부터 결정됩니다:
-
정수 기수(radix)는 문자열의 처음 두 문자를 조사하여 다음과 같이 결정됩니다:
0b는 기수 2를 나타냅니다.0o는 기수 8을 나타냅니다.0x는 기수 16을 나타냅니다.- 그 외의 경우 기수는 10입니다.
- 기수가 10이 아닌 경우, 처음 두 문자가 문자열에서 제거됩니다.
- 모든 접미사는 문자열에서 제거됩니다.
- 모든 밑줄(
_)은 문자열에서 제거됩니다.
- 문자열은 선택된 기수와 함께
u128::from_str_radix를 사용한 것처럼u128값으로 변환됩니다. 만약 값이u128에 맞지 않으면 컴파일 에러입니다.
u128값은 숫자 캐스트(numeric cast) 를 통해 표현식의 타입으로 변환됩니다.
Note
The final cast will truncate the value of the literal if it does not fit in the expression’s type.
rustcincludes a lint check namedoverflowing_literals, defaulting todeny, which rejects expressions where this occurs.
Note
-1i8, for example, is an application of the negation operator to the literal expression1i8, not a single integer literal expression. See Overflow for notes on representing the most negative value for a signed type.
부동 소수점 리터럴 표현식
부동 소수점 리터럴 표현식은 다음 두 가지 형식 중 하나를 가집니다:
- a single FLOAT_LITERAL token
- a single INTEGER_LITERAL token which has a suffix and no radix indicator
토큰에 접미사(suffix) 가 있는 경우, 접미사는 기본 부동 소수점 타입 중 하나인 f32 또는 f64 여야 하며, 표현식은 해당 타입을 가집니다.
토큰에 접미사가 없는 경우, 표현식의 타입은 타입 추론에 의해 결정됩니다.
- 주변 프로그램 문맥으로부터 부동 소수점 타입을 유일하게 결정할 수 있다면, 표현식은 해당 타입을 가집니다.
- 프로그램 문맥이 타입을 충분히 제약하지 않는 경우, 기본값으로
f64가 사용됩니다.
- 프로그램 문맥이 타입을 과도하게 제약하는 경우, 정적 타입 에러로 간주됩니다.
부동 소수점 리터럴 표현식의 예시:
#![allow(unused)]
fn main() {
123.0f64; // f64 타입
0.1f64; // f64 타입
0.1f32; // f32 타입
12E+99_f64; // f64 타입
5f32; // f32 타입
let x: f64 = 2.; // f64 타입
}
표현식의 값은 다음과 같이 토큰의 문자열 표현으로부터 결정됩니다:
- 모든 접미사는 문자열에서 제거됩니다.
- 모든 밑줄(
_)은 문자열에서 제거됩니다.
- 문자열은
f32::from_str또는f64::from_str을 사용한 것처럼 표현식의 타입으로 변환됩니다.
Note
-1.0, for example, is an application of the negation operator to the literal expression1.0, not a single floating-point literal expression.
Note
infandNaNare not literal tokens. Thef32::INFINITY,f64::INFINITY,f32::NAN, andf64::NANconstants can be used instead of literal expressions. Inrustc, a literal large enough to be evaluated as infinite will trigger theoverflowing_literalslint check.
불리언 리터럴 표현식
불리언 리터럴 표현식은 true 또는 false 키워드 중 하나로 구성됩니다.
표현식의 타입은 기본 불리언 타입 이며, 그 값은 다음과 같습니다:
- 키워드가
true인 경우 true - 키워드가
false인 경우 false
경로 표현식
표현식 컨텍스트에서 사용된 경로 는 지역 변수 또는 아이템을 나타냅니다.
Path expressions that resolve to local or static variables are place expressions; other paths are value expressions.
static mut 변수를 사용하는 것은 unsafe 블록 을 요구합니다.
#![allow(unused)]
fn main() {
mod globals {
pub static STATIC_VAR: i32 = 5;
pub static mut STATIC_MUT_VAR: i32 = 7;
}
let local_var = 3;
local_var;
globals::STATIC_VAR;
unsafe { globals::STATIC_MUT_VAR };
let some_constructor = Some::<i32>;
let push_integer = Vec::<i32>::push;
let slice_reverse = <[i32]>::reverse;
}
연관 상수의 평가는 const 블록 과 동일한 방식으로 처리됩니다.
블록 표현식
Syntax
BlockExpression →
{
InnerAttribute*
Statements?
}
Statements →
Statement+
| Statement+ ExpressionWithoutBlock
| ExpressionWithoutBlock
블록 표현식(block expression) 또는 줄여서 블록 은 제어 흐름 표현식이자 아이템 및 변수 선언을 위한 익명 네임스페이스 스코프입니다.
제어 흐름 표현식으로서 블록은 구성 요소인 비-아이템 선언 구문들을 순차적으로 실행한 후, 마지막의 선택적 표현식을 실행합니다.
익명 네임스페이스 스코프로서 아이템 선언은 블록 내부에서만 스코프 내에 있으며, let 문에 의해 선언된 변수들은 다음 구문부터 블록 끝까지 스코프 내에 있습니다. 자세한 내용은 스코프 장을 참조하세요.
블록의 구문은 { 로 시작하여 내부 속성들(../attributes.md), 임의의 개수의 구문들(../statements.md), 마지막 피연산자라고 불리는 선택적 표현식, 그리고 마지막으로 } 가 오는 형태입니다.
구문 뒤에는 보통 세미콜론이 붙어야 하지만, 다음 두 가지 예외가 있습니다:
- 아이템 선언 구문 뒤에는 세미콜론이 붙을 필요가 없습니다.
- 표현식 구문은 보통 뒤에 세미콜론이 필요하지만, 해당 구문의 외부 표현식이 흐름 제어 표현식인 경우는 제외합니다.
또한, 구문들 사이에 추가적인 세미콜론을 사용하는 것이 허용되지만, 이러한 세미콜론들은 시맨틱(의미론)에 영향을 주지 않습니다.
When evaluating a block expression, each statement, except for item declaration statements, is executed sequentially.
그 후, 마지막 피연산자가 주어진 경우 이를 실행합니다.
When a block contains a final operand, the block has the type and value of that final operand.
#![allow(unused)]
fn main() {
let x: u8 = { 0u8 }; // `0u8` is the final operand.
assert_eq!(x, 0);
let x: u8 = { (); 0u8 }; // As above.
assert_eq!(x, 0);
}
When a block does not contain a final operand and the block does not diverge, the block has unit type and unit value.
#![allow(unused)]
fn main() {
let x: () = {}; // Has no final operand.
assert_eq!(x, ());
let x: () = { 0u8; }; // As above.
assert_eq!(x, ());
}
When a block does not contain a final operand and the block diverges, the block has the never type and has no final value (because its type is uninhabited).
#![allow(unused)]
fn main() {
fn f() -> ! { loop {}; } // Diverges and has no final operand.
// ^^^^^^^^^^^^
// The body of a function is a block expression.
}
Note
Observe that a block having no final operand is distinct from having an explicit final operand with unit type. E.g., even though this block diverges, the type of the block is unit rather than never.
#![allow(unused)] fn main() { fn f() -> ! { loop {}; () } // ERROR: Mismatched types. // ^^^^^^^^^^^^^^^ This block has unit type. }
Note
As a control flow expression, if a block expression is the outer expression of an expression statement, the expected type is
()unless it is followed immediately by a semicolon.
A block is considered to be diverging if all reachable control flow paths contain a diverging expression, unless that expression is a place expression that is not read from.
#![allow(unused)]
fn main() {
#![ feature(never_type) ]
fn no_control_flow() -> ! {
// There are no conditional statements, so this entire function body is diverging.
loop {}
}
fn control_flow_diverging() -> ! {
// All paths are diverging, so this entire function body is diverging.
if true {
loop {}
} else {
loop {}
}
}
fn control_flow_not_diverging() -> () {
// Some paths are not diverging, so this entire block is not diverging.
if true {
()
} else {
loop {}
}
}
// Note: This makes use of the unstable never type which is only available on
// Rust's nightly channel. This is done for illustration purposes. It is
// possible to encounter this scenario in stable Rust, but requires a more
// convoluted example.
struct Foo {
x: !,
}
fn make<T>() -> T { loop {} }
fn diverging_place_read() -> ! {
let foo = Foo { x: make() };
// A read of a place expression produces a diverging block.
let _x = foo.x;
}
}
#![allow(unused)]
fn main() {
#![ feature(never_type) ]
fn make<T>() -> T { loop {} }
struct Foo {
x: !,
}
fn diverging_place_not_read() -> ! {
let foo = Foo { x: make() };
// Assignment to `_` means the place is not read.
let _ = foo.x;
} // ERROR: Mismatched types.
}
블록은 항상 값 표현식 이며, 마지막 피연산자를 값 표현식 컨텍스트에서 평가합니다.
Note
This can be used to force moving a value if really needed. For example, the following example fails on the call to
consume_selfbecause the struct was moved out ofsin the block expression.#![allow(unused)] fn main() { struct Struct; impl Struct { fn consume_self(self) {} fn borrow_self(&self) {} } fn move_by_block_expression() { let s = Struct; // 블록 표현식 내에서 `s` 로부터 값을 이동시킵니다. (&{ s }).borrow_self(); // `s` 가 이동되었으므로 실행에 실패합니다. s.consume_self(); } }
async 블록
Syntax
AsyncBlockExpression → async move? BlockExpression
비동기 블록(async block) 은 퓨처(future)로 평가되는 블록 표현식의 변형입니다.
블록의 마지막 표현식이 존재하는 경우, 퓨처의 결과 값을 결정합니다.
비동기 블록을 실행하는 것은 클로저 표현식을 실행하는 것과 유사합니다. 즉각적인 효과는 익명 타입을 생성하고 반환하는 것입니다.
클로저는 하나 이상의 std::ops::Fn 트레잇을 구현하는 타입을 반환하는 반면, 비동기 블록에 대해 반환되는 타입은 std::future::Future 트레잇을 구현합니다.
이 타입의 실제 데이터 형식은 지정되어 있지 않습니다.
Note
The future type that rustc generates is roughly equivalent to an enum with one variant per
awaitpoint, where each variant stores the data needed to resume from its corresponding point.
2018 Edition differences
Async blocks are only available beginning with Rust 2018.
캡처 모드
비동기 블록은 클로저와 동일한 캡처 모드 를 사용하여 환경으로부터 변수를 캡처합니다. 클로저와 마찬가지로, async { .. } 라고 작성하면 각 변수에 대한 캡처 모드가 블록의 내용으로부터 추론됩니다. 반면 async move { .. } 블록은 참조된 모든 변수를 결과 퓨처로 이동(move)시킵니다.
비동기 컨텍스트
비동기 블록은 퓨처를 생성하므로, 다시 await 표현식 을 포함할 수 있는 비동기 컨텍스트(async context) 를 정의합니다. 비동기 컨텍스트는 비동기 블록뿐만 아니라, 비동기 블록의 관점에서 시맨틱이 정의되는 비동기 함수의 본문에 의해 형성됩니다.
제어 흐름 연산자
비동기 블록은 클로저와 매우 비슷하게 함수의 경계처럼 작동합니다.
따라서 ? 연산자와 return 표현식은 모두 둘러싼 함수나 다른 컨텍스트가 아닌, 퓨처의 출력에 영향을 줍니다. 즉, 비동기 블록 내에서의 return <expr> 은 <expr> 의 결과를 퓨처의 출력으로 반환합니다. 마찬가지로, <expr>? 가 에러를 전파하면, 해당 에러는 퓨처의 결과로서 전파됩니다.
마지막으로, break 와 continue 키워드는 비동기 블록 밖으로 분기하는 데 사용될 수 없습니다. 따라서 다음은 유효하지 않습니다.
#![allow(unused)]
fn main() {
loop {
async move {
break; // error[E0267]: `async` 블록 내부의 `break`
}
}
}
const 블록
Syntax
ConstBlockExpression → const BlockExpression
const 블록 은 본문이 런타임이 아닌 컴파일 타임에 평가되는 블록 표현식의 변형입니다.
const 블록을 사용하면 새로운 상수 아이템 을 정의할 필요 없이 상수 값을 정의할 수 있으며, 이러한 이유로 때때로 인라인 상수(inline consts) 라고도 불립니다. 또한 타입 추론을 지원하므로 상수 아이템 과 달리 타입을 명시할 필요가 없습니다.
const 블록은 자유(free) 상수 아이템과 달리 스코프 내의 제네릭 매개변수를 참조할 수 있는 능력이 있습니다. 이들은 스코프 내의 제네릭 매개변수를 가진 상수 아이템으로 디슈거링(desugared)됩니다 (연관 상수와 유사하지만, 연관된 트레잇이나 타입이 없는 형태입니다). 예를 들어, 다음 코드는:
#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
const { std::mem::size_of::<T>() + 1 }
}
}
다음과 동일합니다:
#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
{
struct Const<T>(T);
impl<T> Const<T> {
const CONST: usize = std::mem::size_of::<T>() + 1;
}
Const::<T>::CONST
}
}
}
const 블록 표현식이 런타임에 실행되면, 반환 값이 무시되더라도 해당 상수는 반드시 평가됨이 보장됩니다:
#![allow(unused)]
fn main() {
fn foo<T>() -> usize {
// 이 코드가 실행된다면, 해당 어설션(assertion)은 분명히 컴파일 타임에
// 평가된 것입니다.
const { assert!(std::mem::size_of::<T>() > 0); }
// 여기서 타입이 0 크기가 아님에 의존하는 unsafe 코드를 가질 수 있습니다.
/* ... */
42
}
}
const 블록 표현식이 런타임에 실행되지 않는 경우, 평가될 수도 있고 되지 않을 수도 있습니다:
#![allow(unused)]
fn main() {
if false {
// 프로그램이 빌드될 때 패닉이 발생할 수도 있고 발생하지 않을 수도 있습니다.
const { panic!(); }
}
}
unsafe 블록
Syntax
UnsafeBlockExpression → unsafe BlockExpression
unsafe 를 언제 사용해야 하는지에 대한 자세한 내용은 unsafe 블록 을 참조하세요.
코드 블록 앞에 unsafe 키워드를 붙여 안전하지 않은 연산(unsafe operations) 을 허용할 수 있습니다. 예시:
#![allow(unused)]
fn main() {
unsafe {
let b = [13u8, 17u8];
let a = &b[0] as *const u8;
assert_eq!(*a, 13);
assert_eq!(*a.offset(1), 17);
}
unsafe fn an_unsafe_fn() -> i32 { 10 }
let a = unsafe { an_unsafe_fn() };
}
Labeled block expressions
Labeled block expressions are documented in the Loops and other breakable expressions section.
블록 표현식의 속성
내부 속성 은 다음 상황에서 블록 표현식의 여는 중괄호 바로 뒤에 허용됩니다.
- 함수 및 메서드 본문.
- Loop bodies (
loop,while, andfor). - 구문 으로 사용된 블록 표현식.
- 배열 표현식, 튜플 표현식, 호출 표현식, 그리고 튜플 스타일 구조체 표현식의 요소로서의 블록 표현식.
- 다른 블록 표현식의 꼬리 표현식으로서의 블록 표현식.
블록 표현식에서 의미가 있는 속성은 cfg 와 린트 검사 속성 입니다.
예를 들어, 이 함수는 유닉스 플랫폼에서는 true 를 반환하고 다른 플랫폼에서는 false 를 반환합니다.
#![allow(unused)]
fn main() {
fn is_unix_platform() -> bool {
#[cfg(unix)] { true }
#[cfg(not(unix))] { false }
}
}
연산자 표현식
Syntax
OperatorExpression →
BorrowExpression
| DereferenceExpression
| TryPropagationExpression
| NegationExpression
| ArithmeticOrLogicalExpression
| ComparisonExpression
| LazyBooleanExpression
| TypeCastExpression
| AssignmentExpression
| CompoundAssignmentExpression
연산자는 러스트 언어에 의해 내장 타입에 대해 정의됩니다.
다음 연산자 중 다수는 std::ops 또는 std::cmp 의 트레잇을 사용하여 오버로딩할 수도 있습니다.
오버플로
정수 연산자는 디버그 모드에서 컴파일될 때 오버플로가 발생하면 패닉을 일으킵니다. -C debug-assertions 및 -C overflow-checks 컴파일러 플래그를 사용하여 이를 더 직접적으로 제어할 수 있습니다. 다음 사항들은 오버플로로 간주됩니다:
+,*또는 이항-가 저장할 수 있는 최대값보다 크거나 최소값보다 작은 값을 생성할 때.
- 피연산자가 리터럴 표현식 (또는 하나 이상의 그룹화된 표현식 내에 단독으로 있는 리터럴 표현식)이 아닌 경우, 부호 있는 정수 타입의 가장 작은 음수 값에 단항
-를 적용할 때.
- 왼쪽 인수가 부호 있는 정수 타입의 가장 작은 정수이고 오른쪽 인수가
-1인 경우/또는%사용. 이러한 검사는 레거시 이유로-C overflow-checks가 비활성화된 경우에도 발생합니다.
- 오른쪽 인수가 왼쪽 인수 타입의 비트 수보다 크거나 같거나 음수인 경우
<<또는>>사용.
Note
The exception for literal expressions behind unary
-means that forms such as-128_i8orlet j: i8 = -(128)never cause a panic and have the expected value of -128.이러한 경우, 정수 리터럴 표현식 의 설명에 따라 정수 리터럴이 해당 타입으로 잘리기 때문에 리터럴 표현식은 이미 해당 타입에 대해 가장 작은 음수 값을 가집니다(예:
128_i8은 값 -128을 가짐).이러한 가장 작은 음수 값의 부정은 2의 보수 오버플로 규칙으로 인해 값이 변경되지 않고 그대로 유지됩니다.
rustc에서 이러한 가장 작은 음수 표현식은overflowing_literals린트 검사에서도 무시됩니다.
차용 연산자
Syntax
BorrowExpression →
( & | && ) Expression
| ( & | && ) mut Expression
| ( & | && ) raw const Expression
| ( & | && ) raw mut Expression
& (공유 차용) 및 &mut (가변 차용) 연산자는 단항 접두사 연산자입니다.
장소 표현식 에 적용될 때, 이 표현식은 값이 참조하는 위치에 대한 참조(포인터)를 생성합니다.
메모리 위치는 또한 참조 기간 동안 차용 상태로 놓입니다. 공유 차용(&)의 경우, 이는 장소를 변경할 수 없지만 읽거나 다시 공유할 수 있음을 의미합니다. 가변 차용(&mut)의 경우, 차용이 만료될 때까지 어떤 방식으로도 장소에 접근할 수 없습니다.
&mut 은 피연산자를 가변 장소 표현식 컨텍스트에서 평가합니다.
& 또는 &mut 연산자가 값 표현식 에 적용되면 임시 값 이 생성됩니다.
이 연산자들은 오버로딩할 수 없습니다.
#![allow(unused)]
fn main() {
{
// 이 스코프 동안 지속되는 값 7을 가진 임시 값이 생성됩니다.
let shared_reference = &7;
}
let mut array = [-2, 3, 9];
{
// 이 스코프 동안 `array` 를 가변적으로 차용합니다.
// `array` 는 `mutable_reference` 를 통해서만 사용할 수 있습니다.
let mutable_reference = &mut array;
}
}
&& 가 단일 토큰(지연 ‘and’ 연산자)임에도 불구하고, 차용 표현식의 컨텍스트에서 사용될 때는 두 번의 차용으로 작동합니다:
#![allow(unused)]
fn main() {
// 같은 의미:
let a = && 10;
let a = & & 10;
// 같은 의미:
let a = &&&& mut 10;
let a = && && mut 10;
let a = & & & & mut 10;
}
원시 차용 연산자
&raw const 와 &raw mut 는 원시 차용 연산자 입니다.
이 연산자들의 피연산자 표현식은 장소 표현식 컨텍스트에서 평가됩니다.
&raw const expr 은 주어진 장소에 대한 *const T 타입의 상수 원시 포인터를 생성하고, &raw mut expr 은 *mut T 타입의 가변 원시 포인터를 생성합니다.
장소 표현식이 적절하게 정렬되지 않은 장소로 평가되거나 해당 타입에 의해 결정된 유효한 값을 저장하지 않을 수 있는 경우, 또는 참조를 생성하면 잘못된 앨리어싱 가정이 도입되는 경우 항상 차용 연산자 대신 원시 차용 연산자를 사용해야 합니다. 이러한 상황에서 차용 연산자를 사용하면 잘못된 참조가 생성되어 정의되지 않은 동작 을 유발할 수 있지만, 원시 포인터는 여전히 생성될 수 있습니다.
다음은 packed 구조체를 통해 정렬되지 않은 장소에 대한 원시 포인터를 생성하는 예입니다:
#![allow(unused)]
fn main() {
#[repr(packed)]
struct Packed {
f1: u8,
f2: u16,
}
let packed = Packed { f1: 1, f2: 2 };
// `&packed.f2` 는 정렬되지 않은 참조를 생성하므로 정의되지 않은 동작이 됩니다!
let raw_f2 = &raw const packed.f2;
assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);
}
다음은 유효한 값을 포함하지 않는 장소에 대한 원시 포인터를 생성하는 예입니다:
#![allow(unused)]
fn main() {
use std::mem::MaybeUninit;
struct Demo {
field: bool,
}
let mut uninit = MaybeUninit::<Demo>::uninit();
// `&uninit.as_mut().field` 는 초기화되지 않은 `bool` 에 대한 참조를 생성하므로,
// 정의되지 않은 동작이 됩니다!
let f1_ptr = unsafe { &raw mut (*uninit.as_mut_ptr()).field };
unsafe { f1_ptr.write(true); }
let init = unsafe { uninit.assume_init() };
}
역참조 연산자
Syntax
DereferenceExpression → * Expression
* (역참조) 연산자는 또한 단항 접두사 연산자입니다.
포인터 에 적용될 때, 이는 가리키는 위치를 나타냅니다.
표현식이 &mut T 또는 *mut T 타입이고, 지역 변수, 지역 변수의 (중첩된) 필드 또는 가변 장소 표현식 인 경우, 결과 메모리 위치에 할당할 수 있습니다.
원시 포인터를 역참조하려면 unsafe 가 필요합니다.
비 포인터 타입에서 *x 는 불변 장소 표현식 컨텍스트 에서는 *std::ops::Deref::deref(&x) 와 동일하고, 가변 장소 표현식 컨텍스트에서는 *std::ops::DerefMut::deref_mut(&mut x) 와 동일합니다.
#![allow(unused)]
fn main() {
let x = &7;
assert_eq!(*x, 7);
let y = &mut 9;
*y = 11;
assert_eq!(*y, 11);
}
The try propagation expression
Syntax
TryPropagationExpression → Expression ?
The try propagation expression uses the value of the inner expression and the Try trait to decide whether to produce a value, and if so, what value to produce, or whether to return a value to the caller, and if so, what value to return.
Example
#![allow(unused)] fn main() { use std::num::ParseIntError; fn try_to_parse() -> Result<i32, ParseIntError> { let x: i32 = "123".parse()?; // `x` is `123`. let y: i32 = "24a".parse()?; // Returns an `Err()` immediately. Ok(x + y) // 실행되지 않습니다. } let res = try_to_parse(); println!("{res:?}"); assert!(res.is_err()) }#![allow(unused)] fn main() { fn try_option_some() -> Option<u8> { let val = Some(1)?; Some(val) } assert_eq!(try_option_some(), Some(1)); fn try_option_none() -> Option<u8> { let val = None?; Some(val) } assert_eq!(try_option_none(), None); }use std::ops::ControlFlow; pub struct TreeNode<T> { value: T, left: Option<Box<TreeNode<T>>>, right: Option<Box<TreeNode<T>>>, } impl<T> TreeNode<T> { pub fn traverse_inorder<B>(&self, f: &mut impl FnMut(&T) -> ControlFlow<B>) -> ControlFlow<B> { if let Some(left) = &self.left { left.traverse_inorder(f)?; } f(&self.value)?; if let Some(right) = &self.right { right.traverse_inorder(f)?; } ControlFlow::Continue(()) } } fn main() { let n = TreeNode { value: 1, left: Some(Box::new(TreeNode{value: 2, left: None, right: None})), right: None, }; let v = n.traverse_inorder(&mut |t| { if *t == 2 { ControlFlow::Break("found") } else { ControlFlow::Continue(()) } }); assert_eq!(v, ControlFlow::Break("found")); }
Note
The
Trytrait is currently unstable, and thus cannot be implemented for user types.The try propagation expression is currently roughly equivalent to:
#![allow(unused)] fn main() { #![ feature(try_trait_v2) ] fn example() -> Result<(), ()> { let expr = Ok(()); match core::ops::Try::branch(expr) { core::ops::ControlFlow::Continue(val) => val, core::ops::ControlFlow::Break(residual) => return core::ops::FromResidual::from_residual(residual), } Ok(()) } }
Note
The try propagation operator is sometimes called the question mark operator, the
?operator, or the try operator.
The try propagation operator can be applied to expressions with the type of:
Result<T, E>Result::Ok(val)evaluates toval.Result::Err(e)returnsResult::Err(From::from(e)).
Option<T>Option::Some(val)evaluates toval.Option::NonereturnsOption::None.
ControlFlow<B, C>ControlFlow::Continue(c)evaluates toc.ControlFlow::Break(b)returnsControlFlow::Break(b).
Poll<Result<T, E>>Poll::Ready(Ok(val))evaluates toPoll::Ready(val).Poll::Ready(Err(e))returnsPoll::Ready(Err(From::from(e))).Poll::Pendingevaluates toPoll::Pending.
Poll<Option<Result<T, E>>>Poll::Ready(Some(Ok(val)))evaluates toPoll::Ready(Some(val)).Poll::Ready(Some(Err(e)))returnsPoll::Ready(Some(Err(From::from(e)))).Poll::Ready(None)evaluates toPoll::Ready(None).Poll::Pendingevaluates toPoll::Pending.
부정 연산자
Syntax
NegationExpression →
- Expression
| ! Expression
이것들은 마지막 두 단항 연산자입니다.
이 표는 기본 타입에 대한 동작과 다른 타입에 대해 이 연산자를 오버로딩하는 데 사용되는 트레잇을 요약합니다. 부호 있는 정수는 항상 2의 보수를 사용하여 표현된다는 것을 기억하십시오. 이러한 모든 연산자의 피연산자는 값 표현식 컨텍스트 에서 평가되므로 이동하거나 복사됩니다.
| 기호 | 정수 | bool | 부동 소수점 | 오버로딩 트레잇 |
|---|---|---|---|---|
- | 부정* | 부정 | std::ops::Neg | |
! | 비트 NOT | 논리적 NOT | std::ops::Not |
- 부호 있는 정수 타입에만 해당.
다음은 이러한 연산자의 몇 가지 예입니다
#![allow(unused)]
fn main() {
let x = 6;
assert_eq!(-x, -6);
assert_eq!(!x, -7);
assert_eq!(true, !false);
}
산술 및 논리 이항 연산자
Syntax
ArithmeticOrLogicalExpression →
Expression + Expression
| Expression - Expression
| Expression * Expression
| Expression / Expression
| Expression % Expression
| Expression & Expression
| Expression | Expression
| Expression ^ Expression
| Expression << Expression
| Expression >> Expression
이항 연산자 표현식은 모두 중위 표기법으로 작성됩니다.
이 표는 기본 타입에 대한 산술 및 논리 이항 연산자의 동작과 다른 타입에 대해 이 연산자를 오버로딩하는 데 사용되는 트레잇을 요약합니다. 부호 있는 정수는 항상 2의 보수를 사용하여 표현된다는 것을 기억하십시오. 이러한 모든 연산자의 피연산자는 값 표현식 컨텍스트 에서 평가되므로 이동하거나 복사됩니다.
| 기호 | 정수 | bool | 부동 소수점 | 오버로딩 트레잇 | 복합 할당 트레잇 오버로딩 |
|---|---|---|---|---|---|
+ | 덧셈 | 덧셈 | std::ops::Add | std::ops::AddAssign | |
- | 뺄셈 | 뺄셈 | std::ops::Sub | std::ops::SubAssign | |
* | 곱셈 | 곱셈 | std::ops::Mul | std::ops::MulAssign | |
/ | 나눗셈*† | 나눗셈 | std::ops::Div | std::ops::DivAssign | |
% | 나머지**† | 나머지 | std::ops::Rem | std::ops::RemAssign | |
& | 비트 AND | 논리적 AND | std::ops::BitAnd | std::ops::BitAndAssign | |
| | 비트 OR | 논리적 OR | std::ops::BitOr | std::ops::BitOrAssign | |
^ | 비트 XOR | 논리적 XOR | std::ops::BitXor | std::ops::BitXorAssign | |
<< | 왼쪽 시프트 | std::ops::Shl | std::ops::ShlAssign | ||
>> | 오른쪽 시프트*** | std::ops::Shr | std::ops::ShrAssign |
- 정수 나눗셈은 0을 향해 반올림합니다.
** Rust는 절단 나눗셈 으로 정의된 나머지를 사용합니다. remainder = dividend % divisor 라고 할 때, 나머지는 피제수(dividend)와 같은 부호를 가집니다.
*** 부호 있는 정수 타입에서는 산술 오른쪽 시프트, 부호 없는 정수 타입에서는 논리 오른쪽 시프트입니다.
† 정수 타입의 경우, 0으로 나누면 패닉이 발생합니다.
다음은 이러한 연산자가 사용되는 예입니다.
#![allow(unused)]
fn main() {
assert_eq!(3 + 6, 9);
assert_eq!(5.5 - 1.25, 4.25);
assert_eq!(-5 * 14, -70);
assert_eq!(14 / 3, 4);
assert_eq!(100 % 7, 2);
assert_eq!(0b1010 & 0b1100, 0b1000);
assert_eq!(0b1010 | 0b1100, 0b1110);
assert_eq!(0b1010 ^ 0b1100, 0b110);
assert_eq!(13 << 3, 104);
assert_eq!(-10 >> 2, -3);
}
비교 연산자
Syntax
ComparisonExpression →
Expression == Expression
| Expression != Expression
| Expression > Expression
| Expression < Expression
| Expression >= Expression
| Expression <= Expression
비교 연산자는 기본 타입과 표준 라이브러리의 많은 타입에 대해 정의되어 있습니다.
비교 연산자를 연결할 때는 괄호가 필요합니다. 예를 들어, 표현식 a == b == c 는 유효하지 않으며 (a == b) == c 로 작성해야 합니다.
산술 및 논리 연산자와 달리, 이 연산자를 오버로딩하는 트레잇은 타입이 비교되는 방식을 보여주는 데 더 일반적으로 사용되며, 이러한 트레잇을 바운드로 사용하는 함수에 의해 실제 비교를 정의한다고 가정될 가능성이 높습니다. 표준 라이브러리의 많은 함수와 매크로는 이러한 가정을 사용할 수 있습니다(안전성을 보장하기 위한 것은 아니지만).
위의 산술 및 논리 연산자와 달리, 이 연산자들은 암시적으로 피연산자의 공유 차용을 취하여 장소 표현식 컨텍스트 에서 평가합니다:
#![allow(unused)]
fn main() {
let a = 1;
let b = 1;
a == b;
// 다음과 동일합니다
::std::cmp::PartialEq::eq(&a, &b);
}
이는 피연산자를 밖으로 이동시킬 필요가 없음을 의미합니다.
| 기호 | 의미 | 오버로딩 메서드 |
|---|---|---|
== | 같음 | std::cmp::PartialEq::eq |
!= | 같지 않음 | std::cmp::PartialEq::ne |
> | 보다 큼 | std::cmp::PartialOrd::gt |
< | 보다 작음 | std::cmp::PartialOrd::lt |
>= | 보다 크거나 같음 | std::cmp::PartialOrd::ge |
<= | 보다 작거나 같음 | std::cmp::PartialOrd::le |
다음은 비교 연산자가 사용되는 예입니다.
#![allow(unused)]
fn main() {
assert!(123 == 123);
assert!(23 != -12);
assert!(12.5 > 12.2);
assert!([1, 2, 3] < [1, 3, 4]);
assert!('A' <= 'B');
assert!("World" >= "Hello");
}
지연 불리언 연산자
Syntax
LazyBooleanExpression →
Expression || Expression
| Expression && Expression
The operators || and && may be applied to operands of boolean type. The || operator denotes logical ‘or’, and the && operator denotes logical ‘and’.
이들은 | 및 & 와 달리, 왼쪽 피연산자가 표현식의 결과를 이미 결정하지 않은 경우에만 오른쪽 피연산자가 평가된다는 점에서 다릅니다. 즉, || 는 왼쪽 피연산자가 false 로 평가될 때만 오른쪽 피연산자를 평가하고, && 는 true 로 평가될 때만 평가합니다.
#![allow(unused)]
fn main() {
let x = false || true; // 참(true)
let y = false && panic!(); // 거짓(false), `panic!()` 을 평가하지 않음
}
타입 캐스트 표현식
Syntax
TypeCastExpression → Expression as TypeNoBounds
타입 캐스트 표현식은 이항 연산자 as 로 표시됩니다.
as 표현식을 실행하면 왼쪽의 값이 오른쪽의 타입으로 캐스팅됩니다.
as 표현식의 예:
#![allow(unused)]
fn main() {
fn sum(values: &[f64]) -> f64 { 0.0 }
fn len(values: &[f64]) -> i32 { 0 }
fn average(values: &[f64]) -> f64 {
let sum: f64 = sum(values);
let size: f64 = len(values) as f64;
sum / size
}
}
as 는 강제 변환 을 명시적으로 수행하는 데 사용될 수 있으며, 다음의 추가 캐스트에도 사용됩니다. 강제 변환 규칙이나 표의 항목에 맞지 않는 캐스트는 컴파일러 오류입니다. 여기서 *T 는 *const T 또는 *mut T 를 의미합니다. m 은 참조 타입에서 선택적 mut 를 나타내고, 포인터 타입에서는 mut 또는 const 를 나타냅니다.
e 의 타입 | U | e as U 에 의해 수행되는 캐스트 |
|---|---|---|
| 정수 또는 부동 소수점 타입 | 정수 또는 부동 소수점 타입 | 숫자 캐스트 |
| 열거형 | 정수 타입 | 열거형 캐스트 |
bool 또는 char | 정수 타입 | 기본 타입에서 정수로의 캐스트 |
u8 | char | u8 에서 char 로의 캐스트 |
*T | *V (when compatible) | 포인터에서 포인터로의 캐스트 |
*T (여기서 T: Sized) | 정수 타입 | 포인터에서 주소로의 캐스트 |
| 정수 타입 | *V (여기서 V: Sized) | 주소에서 포인터로의 캐스트 |
&m₁ [T; n] | *m₂ T 1 | 배열에서 포인터로의 캐스트 |
*m₁ [T; n] | *m₂ T 1 | 배열에서 포인터로의 캐스트 |
| 함수 아이템 | 함수 포인터 | 함수 아이템에서 함수 포인터로의 캐스트 |
| 함수 아이템 | *V (여기서 V: Sized) | 함수 아이템에서 포인터로의 캐스트 |
| 함수 아이템 | 정수 | 함수 아이템에서 주소로의 캐스트 |
| 함수 포인터 | *V (여기서 V: Sized) | 함수 포인터에서 포인터로의 캐스트 |
| 함수 포인터 | 정수 | 함수 포인터에서 주소로의 캐스트 |
| 클로저 2 | 함수 포인터 | 클로저에서 함수 포인터로의 캐스트 |
의미론
숫자 캐스트
-
같은 크기의 두 정수 간 캐스팅(예: i32 -> u32)은 무연산(no-op)입니다(러스트는 고정 정수의 음수 값에 2의 보수를 사용합니다).
#![allow(unused)] fn main() { assert_eq!(42i8 as u8, 42u8); assert_eq!(-1i8 as u8, 255u8); assert_eq!(255u8 as i8, -1i8); assert_eq!(-1i16 as u16, 65535u16); }
-
더 큰 정수에서 더 작은 정수로의 캐스팅(예: u32 -> u8)은 잘라냅니다(truncate).
#![allow(unused)] fn main() { assert_eq!(42u16 as u8, 42u8); assert_eq!(1234u16 as u8, 210u8); assert_eq!(0xabcdu16 as u8, 0xcdu8); assert_eq!(-42i16 as i8, -42i8); assert_eq!(1234u16 as i8, -46i8); assert_eq!(0xabcdi32 as i8, -51i8); }
-
더 작은 정수에서 더 큰 정수로의 캐스팅(예: u8 -> u32)은 다음과 같습니다.
- 소스가 부호 없으면 0으로 확장(zero-extend)
- 소스가 부호 있으면 부호 확장(sign-extend)
#![allow(unused)] fn main() { assert_eq!(42i8 as i16, 42i16); assert_eq!(-17i8 as i16, -17i16); assert_eq!(0b1000_1010u8 as u16, 0b0000_0000_1000_1010u16, "0으로 확장"); assert_eq!(0b0000_1010i8 as i16, 0b0000_0000_0000_1010i16, "부호 확장 0"); assert_eq!(0b1000_1010u8 as i8 as i16, 0b1111_1111_1000_1010u16 as i16, "부호 확장 1"); }
-
부동 소수점에서 정수로의 캐스팅은 부동 소수점을 0을 향해 반올림합니다.
NaN은0을 반환합니다.INFINITY를 포함하여 최대 정수 값보다 큰 값은 정수 타입의 최대 값으로 포화(saturate)됩니다.NEG_INFINITY를 포함하여 최소 정수 값보다 작은 값은 정수 타입의 최소 값으로 포화(saturate)됩니다.
#![allow(unused)] fn main() { assert_eq!(42.9f32 as i32, 42); assert_eq!(-42.9f32 as i32, -42); assert_eq!(42_000_000f32 as i32, 42_000_000); assert_eq!(std::f32::NAN as i32, 0); assert_eq!(1_000_000_000_000_000f32 as i32, 0x7fffffffi32); assert_eq!(std::f32::NEG_INFINITY as i32, -0x80000000i32); }
-
정수에서 부동 소수점으로의 캐스팅은 가능한 가장 가까운 부동 소수점을 생성합니다 *
- 필요한 경우, 반올림은
roundTiesToEven모드에 따릅니다 *** - 오버플로 시 무한대(입력과 같은 부호)가 생성됩니다
- 참고: 현재 숫자 타입 세트에서는
f32::MAX + (0.5 ULP)보다 크거나 같은 값에 대해u128 as f32에서만 오버플로가 발생할 수 있습니다
#![allow(unused)] fn main() { assert_eq!(1337i32 as f32, 1337f32); assert_eq!(123_456_789i32 as f32, 123_456_790f32, "반올림됨"); assert_eq!(0xffffffff_ffffffff_ffffffff_ffffffff_u128 as f32, std::f32::INFINITY); } - 필요한 경우, 반올림은
-
f32에서 f64로의 캐스팅은 완벽하며 손실이 없습니다
#![allow(unused)] fn main() { assert_eq!(1_234.5f32 as f64, 1_234.5f64); assert_eq!(std::f32::INFINITY as f64, std::f64::INFINITY); assert!((std::f32::NAN as f64).is_nan()); }
-
f64에서 f32로의 캐스팅은 가능한 가장 가까운 f32를 생성합니다 **
- 필요한 경우, 반올림은
roundTiesToEven모드에 따릅니다 *** - 오버플로 시 무한대(입력과 같은 부호)가 생성됩니다
#![allow(unused)] fn main() { assert_eq!(1_234.5f64 as f32, 1_234.5f32); assert_eq!(1_234_567_891.123f64 as f32, 1_234_567_890f32, "반올림됨"); assert_eq!(std::f64::INFINITY as f32, std::f32::INFINITY); assert!((std::f64::NAN as f32).is_nan()); } - 필요한 경우, 반올림은
* 이 반올림 모드와 오버플로 동작을 사용하는 정수-부동 소수점 캐스팅이 하드웨어에서 기본적으로 지원되지 않는 경우, 이러한 캐스팅은 예상보다 느릴 수 있습니다.
** 이 반올림 모드와 오버플로 동작을 사용하는 f64-f32 캐스팅이 하드웨어에서 기본적으로 지원되지 않는 경우, 이러한 캐스팅은 예상보다 느릴 수 있습니다.
*** IEEE 754-2008 §4.3.1에 정의된 대로: 가장 가까운 부동 소수점 숫자를 선택하고, 두 부동 소수점 숫자 사이의 정확히 중간인 경우 최하위 자릿수가 짝수인 것을 선호합니다.
열거형 캐스트
열거형을 판별자(discriminant)로 캐스팅한 다음 필요한 경우 숫자 캐스트를 사용합니다. 캐스팅은 다음 종류의 열거형으로 제한됩니다:
#![allow(unused)]
fn main() {
enum Enum { A, B, C }
assert_eq!(Enum::A as i32, 0);
assert_eq!(Enum::B as i32, 1);
assert_eq!(Enum::C as i32, 2);
}
열거형이 Drop 을 구현하는 경우 캐스팅이 허용되지 않습니다.
기본 타입에서 정수로의 캐스트
false는0으로,true는1로 캐스팅됩니다char는 코드 포인트 값으로 캐스팅된 다음 필요한 경우 숫자 캐스트를 사용합니다.
#![allow(unused)]
fn main() {
assert_eq!(false as i32, 0);
assert_eq!(true as i32, 1);
assert_eq!('A' as i32, 65);
assert_eq!('Ö' as i32, 214);
}
u8 에서 char 로의 캐스트
해당 코드 포인트를 가진 char 로 캐스팅합니다.
#![allow(unused)]
fn main() {
assert_eq!(65u8 as char, 'A');
assert_eq!(214u8 as char, 'Ö');
}
포인터에서 주소로의 캐스트
원시 포인터에서 정수로의 캐스팅은 참조된 메모리의 기계 주소를 생성합니다. 정수 타입이 포인터 타입보다 작은 경우 주소가 잘릴 수 있습니다. usize 를 사용하면 이를 방지할 수 있습니다.
주소에서 포인터로의 캐스트
정수에서 원시 포인터로의 캐스팅은 정수를 메모리 주소로 해석하고 해당 메모리를 참조하는 포인터를 생성합니다.
Warning
This interacts with the Rust memory model, which is still under development. A pointer obtained from this cast may suffer additional restrictions even if it is bitwise equal to a valid pointer. Dereferencing such a pointer may be undefined behavior if aliasing rules are not followed.
건전한 주소 연산의 간단한 예:
#![allow(unused)]
fn main() {
let mut values: [i32; 2] = [1, 2];
let p1: *mut i32 = values.as_mut_ptr();
let first_address = p1 as usize;
let second_address = first_address + 4; // 4 == size_of::<i32>()
let p2 = second_address as *mut i32;
unsafe {
*p2 += 1;
}
assert_eq!(values[1], 3);
}
포인터에서 포인터로의 캐스트
*const T / *mut T 는 다음과 같은 동작으로 *const U / *mut U 로 캐스팅될 수 있습니다:
-
T와U가 모두 크기가 있는(sized) 경우, 포인터는 변경되지 않고 반환됩니다.Example
#![allow(unused)] fn main() { let x: i32 = 42; let p1: *const i32 = &x; let p2: *const u8 = p1 as *const u8; // The pointer address remains the same. assert_eq!(p1 as usize, p2 as usize); }
-
T는 크기가 없고U는 크기가 있는 경우, 캐스트는 넓은 포인터(wide pointer)T를 완성하는 모든 메타데이터를 버리고 크기가 없는 포인터의 데이터 부분으로 구성된 얇은 포인터(thin pointer)U를 생성합니다.Example
#![allow(unused)] fn main() { let slice: &[i32] = &[1, 2, 3]; let ptr: *const [i32] = slice as *const [i32]; // Cast from wide pointer (*const [i32]) to thin pointer (*const i32) // discarding the length metadata. let data_ptr: *const i32 = ptr as *const i32; assert_eq!(unsafe { *data_ptr }, 1); }
- If
TandUare both unsized, the pointer is also returned unchanged. In particular, the metadata is preserved exactly. The cast can only be performed if the metadata is compatible according to the below rules:
-
When
TandUare unsized with slice metadata, they are always compatible. The metadata of a slice is the number of elements, so casting*[u16] -> *[u8]is legal but will result in reducing the number of bytes by half.Example
#![allow(unused)] fn main() { let slice: &[u16] = &[1, 2, 3]; let ptr: *const [u16] = slice as *const [u16]; let byte_ptr: *const [u8] = ptr as *const [u8]; assert_eq!(byte_ptr.len(), 3); }
- When
TandUare unsized with trait object metadata, the metadata is compatible only when all of the following holds:-
The principal trait must be the same.
Example
#![allow(unused)] fn main() { trait Foo {} trait Bar {} impl Foo for i32 {} impl Bar for i32 {} let x: i32 = 42; let ptr_foo: *const dyn Foo = &x as *const dyn Foo; // You can't cast to a different principal trait. let ptr_bar: *const dyn Bar = ptr_foo as *const dyn Bar; // 오류 } -
Auto traits may be removed.
Example
#![allow(unused)] fn main() { trait Foo {} struct S; impl Foo for S {} unsafe impl Send for S {} let s = S; let ptr_send: *const (dyn Foo + Send) = &s; // Removing an auto trait. let ptr_no_send: *const dyn Foo = ptr_send as *const dyn Foo; } -
Auto traits may be added only if they are a super trait of the principal trait.
Example
#![allow(unused)] fn main() { trait Foo: Send {} struct S; impl Foo for S {} unsafe impl Send for S {} let s = S; let ptr_no_send: *const dyn Foo = &s; // Adding an auto trait. let ptr_send: *const (dyn Foo + Send) = ptr_no_send as *const (dyn Foo + Send); }#![allow(unused)] fn main() { trait Foo {} struct S; impl Foo for S {} unsafe impl Send for S {} let s = S; let ptr_no_send: *const dyn Foo = &s; // Same as above, except trait Foo does not have Send as a super trait. let ptr_send: *const (dyn Foo + Send) = ptr_no_send as *const (dyn Foo + Send); // 오류 } -
Trailing lifetimes may only be shortened.
Example
#![allow(unused)] fn main() { trait Foo {} fn shorten_lifetime<'long: 'short, 'short>( ptr: *const (dyn Foo + 'long), ) -> *const (dyn Foo + 'short) { // Shortening the lifetime is allowed. ptr as *const (dyn Foo + 'short) } }#![allow(unused)] fn main() { trait Foo {} fn lengthen_lifetime<'long: 'short, 'short>( ptr: *const (dyn Foo + 'short), ) -> *const (dyn Foo + 'long) { // It is not allowed to cast to a longer lifetime. ptr as *const (dyn Foo + 'long) // 오류 } } -
Generics (including lifetimes) and associated types must match exactly.
Example
#![allow(unused)] fn main() { trait Generic<T> {} impl Generic<i32> for () {} impl Generic<u32> for () {} let x = (); let ptr_i32: *const dyn Generic<i32> = &x; // You can't cast to a different generic parameter. let ptr_u32: *const dyn Generic<u32> = ptr_i32 as *const dyn Generic<u32>; // 오류 }#![allow(unused)] fn main() { trait HasType { type Output; } trait Generic<'x, T> {} fn cast_via_associated<'a, 'b, A, B>( ptr: *const dyn Generic<'a, A::Output>, ) -> *const dyn Generic<'b, B::Output> where 'a: 'b, 'b: 'a, A: HasType, B: HasType<Output = A::Output>, // Forces equality { ptr as *const dyn Generic<'b, B::Output> } }
-
-
When
TorUis a struct or tuple type whose last field is unsized, it has the same metadata and compatibility rules as its last field.Example
#![allow(unused)] fn main() { struct Wrapper(u32, [u8]); let slice: &[u8] = &[1, 2, 3]; let ptr: *const [u8] = slice; // The metadata (length 3) is preserved when casting to a struct // where the last field is the unsized type `[u8]`. let wrapper_ptr: *const Wrapper = ptr as *const Wrapper; // And preserved when casting back. let ptr_back: *const [u8] = wrapper_ptr as *const [u8]; assert_eq!(ptr_back.len(), 3); }
할당 표현식
Syntax
AssignmentExpression → Expression = Expression
할당 표현식 은 값을 지정된 장소로 이동합니다.
할당 표현식은 가변 피할당자 표현식(피할당자 피연산자 )과 그 뒤에 오는 등호(=), 그리고 값 표현식( 할당된 값 피연산자)으로 구성됩니다.
가장 기본적인 형태에서, 피할당자 표현식은 장소 표현식 이며, 이 경우를 먼저 논의합니다.
더 일반적인 구조 분해 할당의 경우는 아래에서 논의되지만, 이 경우는 항상 장소 표현식에 대한 순차적인 할당으로 분해되므로, 장소 표현식 할당이 더 근본적인 경우로 간주될 수 있습니다.
기본 할당
할당 표현식의 평가는 피연산자의 평가로 시작됩니다. 할당된 값 피연산자가 먼저 평가되고, 그 다음에 피할당자 표현식이 평가됩니다.
구조 분해 할당의 경우, 피할당자 표현식의 하위 표현식은 왼쪽에서 오른쪽으로 평가됩니다.
Note
This is different than other expressions in that the right operand is evaluated before the left one.
그런 다음 할당된 장소의 값이 초기화되지 않은 지역 변수나 초기화되지 않은 지역 변수의 필드가 아닌 경우, 할당된 장소의 값을 먼저 드랍 하는 효과를 가집니다.
그 다음 할당된 값을 할당된 장소로 복사하거나 이동 합니다.
할당 표현식은 항상 유닛 값 을 생성합니다.
예:
#![allow(unused)]
fn main() {
let mut x = 0;
let y = 0;
x = y;
}
구조 분해 할당
구조 분해 할당은 변수 선언을 위한 구조 분해 패턴 매칭의 대응으로, 튜플이나 구조체와 같은 복합 값에 대한 할당을 허용합니다. 예를 들어, 두 개의 가변 변수를 교환할 수 있습니다:
#![allow(unused)]
fn main() {
let (mut a, mut b) = (0, 1);
// 구조 분해 할당을 사용하여 `a` 와 `b` 를 교환합니다.
(b, a) = (a, b);
}
let 을 사용한 구조 분해 선언과 달리, 구문상의 모호성 때문에 패턴은 할당의 왼쪽에 나타날 수 없습니다. 대신 패턴에 해당하는 표현식 그룹이 피할당자 표현식 으로 지정되어 할당의 왼쪽에 허용됩니다. 피할당자 표현식은 그런 다음 패턴 매칭과 순차적 할당으로 탈설탕(desugared)됩니다.
탈설탕된 패턴은 반박할 수 없어야 합니다(irrefutable). 특히 이는 컴파일 타임에 길이가 알려진 슬라이스 패턴과 사소한 슬라이스 [..] 만이 구조 분해 할당에 허용됨을 의미합니다.
탈설탕 방법은 간단하며, 예제로 설명하는 것이 가장 좋습니다.
#![allow(unused)]
fn main() {
struct Struct { x: u32, y: u32 }
let (mut a, mut b) = (0, 0);
(a, b) = (3, 4);
[a, b] = [3, 4];
Struct { x: a, y: b } = Struct { x: 3, y: 4};
// 다음과 같이 탈설탕됩니다:
{
let (_a, _b) = (3, 4);
a = _a;
b = _b;
}
{
let [_a, _b] = [3, 4];
a = _a;
b = _b;
}
{
let Struct { x: _a, y: _b } = Struct { x: 3, y: 4};
a = _a;
b = _b;
}
}
단일 피할당자 표현식에서 식별자를 여러 번 사용하는 것은 금지되지 않습니다.
밑줄 표현식 과 빈 범위 표현식 을 사용하여 특정 값을 바인딩하지 않고 무시할 수 있습니다.
탈설탕된 표현식에는 기본 바인딩 모드가 적용되지 않는다는 점에 유의하십시오.
Note
The desugaring restricts the temporary scope of the assigned value operand (the RHS) of a destructuring assignment.
In a basic assignment, the temporary is dropped at the end of the enclosing temporary scope. Below, that’s the statement. Therefore, the assignment and use is allowed.
#![allow(unused)] fn main() { fn temp() {} fn f<T>(x: T) -> T { x } let x; (x = f(&temp()), x); // OK }Conversely, in a destructuring assignment, the temporary is dropped at the end of the
letstatement in the desugaring. As that happens before we try to assign tox, below, it fails.#![allow(unused)] fn main() { fn temp() {} fn f<T>(x: T) -> T { x } let x; [x] = [f(&temp())]; // 오류 }This desugars to:
#![allow(unused)] fn main() { fn temp() {} fn f<T>(x: T) -> T { x } let x; { let [_x] = [f(&temp())]; // ^ // The temporary is dropped here. x = _x; // 오류 } }
Note
Due to the desugaring, the assigned value operand (the RHS) of a destructuring assignment is an extending expression within a newly-introduced block.
Below, because the temporary scope is extended to the end of this introduced block, the assignment is allowed.
#![allow(unused)] fn main() { fn temp() {} let x; [x] = [&temp()]; // OK }This desugars to:
#![allow(unused)] fn main() { fn temp() {} let x; { let [_x] = [&temp()]; x = _x; } // OK }However, if we try to use
x, even within the same statement, we’ll get an error because the temporary is dropped at the end of this introduced block.#![allow(unused)] fn main() { fn temp() {} let x; ([x] = [&temp()], x); // 오류 }This desugars to:
#![allow(unused)] fn main() { fn temp() {} let x; ( { let [_x] = [&temp()]; x = _x; }, // <-- The temporary is dropped here. x, // 오류 ); }
복합 할당 표현식
Syntax
CompoundAssignmentExpression →
Expression += Expression
| Expression -= Expression
| Expression *= Expression
| Expression /= Expression
| Expression %= Expression
| Expression &= Expression
| Expression |= Expression
| Expression ^= Expression
| Expression <<= Expression
| Expression >>= Expression
복합 할당 표현식 은 산술 및 논리 이항 연산자와 할당 표현식을 결합한 것입니다.
예:
#![allow(unused)]
fn main() {
let mut x = 5;
x += 1;
assert!(x == 6);
}
복합 할당의 구문은 가변 장소 표현식(할당된 피연산자 ), 그 다음에 단일 토큰(공백 없음)으로 = 이 뒤따르는 연산자 중 하나, 그리고 값 표현식( 수정하는 피연산자)입니다.
다른 장소 피연산자와 달리, 할당된 장소 피연산자는 반드시 장소 표현식이어야 합니다.
값 표현식을 사용하려고 시도하면 이를 임시 값으로 승격시키는 대신 컴파일러 오류가 발생합니다.
Evaluation of compound assignment expressions depends on the types of the operands.
If the types of both operands are known, prior to monomorphization, to be primitive, the right hand side is evaluated first, the left hand side is evaluated next, and the place given by the evaluation of the left hand side is mutated by applying the operator to the values of both sides.
use core::{num::Wrapping, ops::AddAssign};
trait Equate {}
impl<T> Equate for (T, T) {}
fn f1(x: (u8,)) {
let mut order = vec![];
// The RHS is evaluated first as both operands are of primitive
// type.
{ order.push(2); x }.0 += { order.push(1); x }.0;
assert!(order.is_sorted());
}
fn f2(x: (Wrapping<u8>,)) {
let mut order = vec![];
// The LHS is evaluated first as `Wrapping<_>` is not a primitive
// type.
{ order.push(1); x }.0 += { order.push(2); (0u8,) }.0;
assert!(order.is_sorted());
}
fn f3<T: AddAssign<u8> + Copy>(x: (T,)) where (T, u8): Equate {
let mut order = vec![];
// The LHS is evaluated first as one of the operands is a generic
// parameter, even though that generic parameter can be unified
// with a primitive type due to the where clause bound.
{ order.push(1); x }.0 += { order.push(2); (0u8,) }.0;
assert!(order.is_sorted());
}
fn main() {
f1((0u8,));
f2((Wrapping(0u8),));
// We supply a primitive type as the generic argument, but this
// does not affect the evaluation order in `f3` when
// monomorphized.
f3::<u8>((0u8,));
}
Note
This is unusual. Elsewhere left to right evaluation is the norm.
See the eval order test for more examples.
Otherwise, this expression is syntactic sugar for using the corresponding trait for the operator (see expr.arith-logic.behavior) and calling its method with the left hand side as the receiver and the right hand side as the next argument.
For example, the following two statements are equivalent:
#![allow(unused)]
fn main() {
use std::ops::AddAssign;
fn f<T: AddAssign + Copy>(mut x: T, y: T) {
x += y; // Statement 1.
x.add_assign(y); // Statement 2.
}
}
Note
Surprisingly, desugaring this further to a fully qualified method call is not equivalent, as there is special borrow checker behavior when the mutable reference to the first operand is taken via autoref.
#![allow(unused)] fn main() { use std::ops::AddAssign; fn f<T: AddAssign + Copy>(mut x: T) { // Here we used `x` as both the LHS and the RHS. Because the // mutable borrow of the LHS needed to call the trait method // is taken implicitly by autoref, this is OK. x += x; //~ OK x.add_assign(x); //~ OK } }#![allow(unused)] fn main() { use std::ops::AddAssign; fn f<T: AddAssign + Copy>(mut x: T) { // We can't desugar the above to the below, as once we take the // mutable borrow of `x` to pass the first argument, we can't // pass `x` by value in the second argument because the mutable // reference is still live. <T as AddAssign>::add_assign(&mut x, x); //~^ ERROR cannot use `x` because it was mutably borrowed } }#![allow(unused)] fn main() { use std::ops::AddAssign; fn f<T: AddAssign + Copy>(mut x: T) { // As above. (&mut x).add_assign(x); //~^ ERROR cannot use `x` because it was mutably borrowed } }
As with normal assignment expressions, compound assignment expressions always produce the unit value.
Warning
Avoid writing code that depends on the evaluation order of operands in compound assignments as it can be unusual and surprising.
-
Only when
m₁ismutorm₂isconst. Castingmutreference/pointer toconstpointer is allowed. ↩ ↩2 -
Only closures that do not capture (close over) any local variables can be cast to function pointers. ↩
그룹화된 표현식
Syntax
GroupedExpression → ( Expression )
괄호로 묶인 표현식 은 단일 표현식을 감싸며 해당 표현식으로 평가됩니다. 괄호로 묶인 표현식의 구문은 (, 그 다음에 둘러싸인 피연산자 라고 하는 표현식, 그리고 ) 입니다.
괄호로 묶인 표현식은 둘러싸인 피연산자의 값으로 평가됩니다.
A parenthesized expression is a place expression if the enclosed operand is a place expression, and is a value expression if the enclosed operand is a value expression.
괄호는 표현식 내의 하위 표현식의 우선 순위를 명시적으로 수정하는 데 사용할 수 있습니다.
괄호로 묶인 표현식의 예:
#![allow(unused)]
fn main() {
let x: i32 = 2 + 3 * 4; // 괄호로 묶이지 않음
let y: i32 = (2 + 3) * 4; // 괄호로 묶임
assert_eq!(x, 14);
assert_eq!(y, 20);
}
괄호가 반드시 필요한 경우의 예는 구조체의 멤버인 함수 포인터를 호출할 때입니다:
#![allow(unused)]
fn main() {
struct A {
f: fn() -> &'static str
}
impl A {
fn f(&self) -> &'static str {
"메서드 f"
}
}
let a = A{f: || "필드 f"};
assert_eq!( a.f (), "메서드 f");
assert_eq!((a.f)(), "필드 f");
}
배열 및 배열 인덱스 표현식
배열 표현식
Syntax
ArrayExpression → [ ArrayElements? ]
ArrayElements →
Expression ( , Expression )* ,?
| Expression ; Expression
배열 표현식 은 배열 을 생성합니다. 배열 표현식에는 두 가지 형식이 있습니다.
첫 번째 형식은 배열의 모든 값을 나열합니다.
이 형식의 구문은 대괄호 안에 쉼표로 구분된 동일한 타입의 표현식 목록입니다.
이것은 각 값을 작성된 순서대로 포함하는 배열을 생성합니다.
두 번째 형식의 구문은 대괄호 안에 세미콜론(;)으로 구분된 두 개의 표현식입니다.
; 앞의 표현식을 반복 피연산자 라고 합니다.
; 뒤의 표현식을 길이 피연산자 라고 합니다.
The length operand must either be an inferred const or be a constant expression of type usize (e.g. a literal or a constant item).
#![allow(unused)]
fn main() {
const C: usize = 1;
let _: [u8; C] = [0; 1]; // Literal.
let _: [u8; C] = [0; C]; // Constant item.
let _: [u8; C] = [0; _]; // Inferred const.
let _: [u8; C] = [0; (((_)))]; // Inferred const.
}
Note
In an array expression, an inferred const is parsed as an expression but then semantically treated as a separate kind of const generic argument.
이 형식의 배열 표현식은 길이 피연산자의 값만큼의 길이를 가지며, 각 요소가 반복 피연산자의 복사본인 배열을 생성합니다. 즉, [a; b] 는 a 값의 복사본 b 개를 포함하는 배열을 생성합니다.
If the length operand has a value greater than 1 then this requires the repeat operand to have a type that implements Copy, to be a const block expression, or to be a path to a constant item.
When the repeat operand is a const block or a path to a constant item, it is evaluated the number of times specified in the length operand.
If that value is 0, then the const block or constant item is not evaluated at all.
For expressions that are neither a const block nor a path to a constant item, it is evaluated exactly once, and then the result is copied the length operand’s value times.
#![allow(unused)]
fn main() {
[1, 2, 3, 4];
["a", "b", "c", "d"];
[0; 128]; // 128개의 0을 가진 배열
[0u8, 0u8, 0u8, 0u8,];
[[1, 0, 0], [0, 1, 0], [0, 0, 1]]; // 2차원 배열
const EMPTY: Vec<i32> = Vec::new();
[EMPTY; 2];
}
배열 및 슬라이스 인덱싱 표현식
Syntax
IndexExpression → Expression [ Expression ]
배열 및 슬라이스 타입의 값은 그 뒤에 usize 타입의 대괄호로 묶인 표현식(인덱스)을 작성하여 인덱싱할 수 있습니다. 배열이 가변인 경우 결과 메모리 위치 에 할당할 수 있습니다.
다른 타입의 경우 인덱스 표현식 a[b] 는 *std::ops::Index::index(&a, b) 와 동일하며, 가변 장소 표현식 컨텍스트에서는 *std::ops::IndexMut::index_mut(&mut a, b) 와 동일합니다. 메서드와 마찬가지로, Rust는 구현을 찾기 위해 a 에 대해 반복적으로 역참조 작업을 수행합니다.
배열과 슬라이스의 인덱스는 0부터 시작합니다.
Array access is a constant expression, so bounds can be checked at compile-time with a constant index value. Otherwise a check will be performed at run-time that will put the thread in a panicked state if it fails.
#![allow(unused)]
fn main() {
// 린트는 기본적으로 거부(deny)됩니다.
#![warn(unconditional_panic)]
([1, 2, 3, 4])[2]; // 3으로 평가됩니다
let b = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
b[1][2]; // 다차원 배열 인덱싱
let x = (["a", "b"])[10]; // 경고: 인덱스가 범위를 벗어남
let n = 10;
let y = (["a", "b"])[n]; // 패닉 발생
let arr = ["a", "b"];
arr[10]; // 경고: 인덱스가 범위를 벗어남
}
배열 인덱스 표현식은 Index 및 IndexMut 트레잇을 구현하여 배열 및 슬라이스 이외의 타입에 대해 구현할 수 있습니다.
튜플 및 튜플 인덱싱 표현식
튜플 표현식
Syntax
TupleExpression → ( TupleElements? )
TupleElements → ( Expression , )+ Expression?
튜플 표현식 은 튜플 값 을 생성합니다.
튜플 표현식의 구문은 튜플 초기화 피연산자 라고 하는 괄호로 묶인 쉼표로 구분된 표현식 목록입니다.
단항(1-ary) 튜플 표현식은 괄호로 묶인 표현식 과 구분하기 위해 튜플 초기화 피연산자 뒤에 쉼표가 필요합니다.
튜플 표현식은 새로 생성된 튜플 타입 값으로 평가되는 값 표현식 입니다.
튜플 초기화 피연산자의 수는 생성된 튜플의 항수(arity)입니다.
튜플 초기화 피연산자가 없는 튜플 표현식은 유닛 튜플을 생성합니다.
다른 튜플 표현식의 경우, 첫 번째로 작성된 튜플 초기화 피연산자는 필드 0 을 초기화하고 후속 피연산자는 다음으로 높은 필드를 초기화합니다. 예를 들어, 튜플 표현식 ('a', 'b', 'c') 에서 'a' 는 필드 0 의 값을 초기화하고, 'b' 는 필드 1, 'c' 는 필드 2 를 초기화합니다.
튜플 표현식과 그 타입의 예:
| 표현식 | 유형 |
|---|---|
() | () (유닛) |
(0.0, 4.5) | (f64, f64) |
("x".to_string(), ) | (String, ) |
("a", 4usize, true) | (&'static str, usize, bool) |
튜플 인덱싱 표현식
Syntax
TupleIndexingExpression → Expression . TUPLE_INDEX
튜플 인덱싱 표현식 은 튜플 과 튜플 구조체 의 필드에 접근합니다.
튜플 인덱스 표현식의 구문은 튜플 피연산자 라고 하는 표현식, 그 다음 ., 그리고 마지막으로 튜플 인덱스입니다.
_튜플 인덱스 _의 구문은 선행 0, 밑줄 또는 접미사가 없는 십진 리터럴 입니다. 예를 들어 0 과 2 는 유효한 튜플 인덱스이지만, 01, 0_, 0i32 는 그렇지 않습니다.
튜플 피연산자의 타입은 튜플 타입 또는 튜플 구조체 여야 합니다.
튜플 인덱스는 튜플 피연산자 타입의 필드 이름이어야 합니다.
튜플 인덱스 표현식의 평가는 튜플 피연산자의 평가 외에 부작용이 없습니다. 장소 표현식 으로서, 튜플 인덱스와 이름이 같은 튜플 피연산자 필드의 위치로 평가됩니다.
튜플 인덱싱 표현식의 예:
#![allow(unused)]
fn main() {
// 튜플 인덱싱
let pair = ("문자열", 2);
assert_eq!(pair.1, 2);
// 튜플 구조체 인덱싱
struct Point(f32, f32);
let point = Point(1.0, 0.0);
assert_eq!(point.0, 1.0);
assert_eq!(point.1, 0.0);
}
Note
Unlike field access expressions, tuple index expressions can be the function operand of a call expression as it cannot be confused with a method call since method names cannot be numbers.
Note
Although arrays and slices also have elements, you must use an array or slice indexing expression or a slice pattern to access their elements.
구조체 표현식
Syntax
StructExpression →
PathInExpression { ( StructExprFields | StructBase )? }
StructExprFields →
StructExprField ( , StructExprField )* ( , StructBase | ,? )
StructExprField →
OuterAttribute*
(
IDENTIFIER
| ( IDENTIFIER | TUPLE_INDEX ) : Expression
)
StructBase → .. Expression
A struct expression creates a struct, enum, or union value. It consists of a path to a struct, enum variant, or union item followed by the values for the fields of the item.
다음은 구조체 표현식의 예입니다:
#![allow(unused)]
fn main() {
struct Point { x: f64, y: f64 }
struct NothingInMe { }
mod game { pub struct User<'a> { pub name: &'a str, pub age: u32, pub score: usize } }
enum Enum { Variant {} }
Point {x: 10.0, y: 20.0};
NothingInMe {};
let u = game::User {name: "Joe", age: 35, score: 100_000};
Enum::Variant {};
}
Note
Tuple structs and tuple enum variants are typically instantiated using a call expression referring to the constructor in the value namespace. These are distinct from a struct expression using curly braces referring to the constructor in the type namespace.
#![allow(unused)] fn main() { struct Position(i32, i32, i32); Position(0, 0, 0); // 튜플 구조체를 생성하는 전형적인 방법. let c = Position; // `c` 는 3개의 인수를 받는 함수입니다. let pos = c(8, 6, 7); // `Position` 값을 생성합니다. enum Version { Triple(i32, i32, i32) }; Version::Triple(0, 0, 0); let f = Version::Triple; let ver = f(8, 6, 7); }The last segment of the call path cannot refer to a type alias:
#![allow(unused)] fn main() { trait Tr { type T; } impl<T> Tr for T { type T = T; } struct Tuple(); enum Enum { Tuple() } // <Unit as Tr>::T(); // causes an error -- `::T` is a type, not a value <Enum as Tr>::T::Tuple(); // OK }
Unit structs and unit enum variants are typically instantiated using a path expression referring to the constant in the value namespace.
#![allow(unused)] fn main() { struct Gamma; // Gamma unit value, referring to the const in the value namespace. let a = Gamma; // Exact same value as `a`, but constructed using a struct expression // referring to the type namespace. let b = Gamma {}; enum ColorSpace { Oklch } let c = ColorSpace::Oklch; let d = ColorSpace::Oklch {}; }
필드 구조체 표현식
중괄호로 묶인 필드가 있는 구조체 표현식을 사용하면 각 개별 필드의 값을 순서에 관계없이 지정할 수 있습니다. 필드 이름은 콜론으로 값과 구분됩니다.
공용체 타입의 값은 이 구문을 사용하여 생성할 수 있으며, 정확히 하나의 필드를 지정해야 합니다.
함수형 업데이트 구문
구조체 타입의 값을 생성하는 구조체 표현식은 함수형 업데이트를 나타내기 위해 .. 뒤에 표현식이 오는 구문으로 끝날 수 있습니다.
.. 뒤에 오는 표현식(베이스)은 생성되는 새 구조체 타입과 동일한 구조체 타입을 가져야 합니다.
전체 표현식은 지정된 필드에 대해 주어진 값을 사용하고 베이스 표현식에서 나머지 필드를 이동하거나 복사합니다.
모든 구조체 표현식과 마찬가지로, 명시적으로 명명되지 않은 필드를 포함하여 구조체의 모든 필드는 보여야 합니다.
#![allow(unused)]
fn main() {
struct Point3d { x: i32, y: i32, z: i32 }
let mut base = Point3d {x: 1, y: 2, z: 3};
let y_ref = &mut base.y;
Point3d {y: 0, z: 10, .. base}; // OK, base.x만 접근됩니다
drop(y_ref);
}
Struct expressions can’t be used directly in a loop or if expression’s head, or in the scrutinee of an if let or match expression. However, struct expressions can be used in these situations if they are within another expression, for example inside parentheses.
필드 이름은 십진 정수 값이 되어 튜플 구조체 생성 시 인덱스를 지정할 수 있습니다. 이것은 기본 구조체와 함께 사용하여 지정되지 않은 나머지 인덱스를 채우는 데 사용할 수 있습니다.
#![allow(unused)]
fn main() {
struct Color(u8, u8, u8);
let c1 = Color(0, 0, 0); // 튜플 구조체를 생성하는 전형적인 방법.
let c2 = Color{0: 255, 1: 127, 2: 0}; // 인덱스로 필드 지정.
let c3 = Color{1: 0, ..c2}; // 기본 구조체를 사용하여 다른 모든 필드를 채웁니다.
}
구조체 필드 초기화 단축형
명명된(번호가 매겨지지 않은) 필드가 있는 데이터 구조(구조체, 열거형, 공용체)를 초기화할 때, fieldname: fieldname 의 약어로 fieldname 을 작성할 수 있습니다. 이를 통해 중복이 적은 간결한 구문을 사용할 수 있습니다. 예를 들면:
#![allow(unused)]
fn main() {
struct Point3d { x: i32, y: i32, z: i32 }
let x = 0;
let y_value = 0;
let z = 0;
Point3d { x: x, y: y_value, z: z };
Point3d { x, y: y_value, z };
}
호출 표현식
Syntax
CallExpression → Expression ( CallParams? )
CallParams → Expression ( , Expression )* ,?
호출 표현식 은 함수를 호출합니다. 호출 표현식의 구문은 함수 피연산자 라고 하는 표현식과 그 뒤에 오는 인수 피연산자 라고 하는 괄호로 묶인 쉼표로 구분된 표현식 목록입니다.
함수가 결국 반환되면 표현식이 완료됩니다.
비 함수 타입 의 경우, 표현식 f(...) 는 함수 피연산자에 따라 다음 트레잇 중 하나의 메서드를 사용합니다:
Fn또는AsyncFn— 공유 참조.FnMut또는AsyncFnMut— 가변 참조.FnOnce또는AsyncFnOnce— 값.
An automatic borrow will be taken if needed. The function operand will also be automatically dereferenced as required.
호출 표현식의 몇 가지 예:
#![allow(unused)]
fn main() {
fn add(x: i32, y: i32) -> i32 { 0 }
let three: i32 = add(1i32, 2i32);
let name: &'static str = (|| "Rust")();
}
Disambiguating function calls
모든 함수 호출은 더 명시적인 정규화된 구문 에 대한 설탕입니다.
함수 호출은 스코프 내 아이템에 비추어 호출의 모호성에 따라 완전히 정규화되어야 할 수도 있습니다.
Note
In the past, the terms “Unambiguous Function Call Syntax”, “Universal Function Call Syntax”, or “UFCS”, have been used in documentation, issues, RFCs, and other community writings. However, these terms lack descriptive power and potentially confuse the issue at hand. We mention them here for searchability’s sake.
메서드 또는 연관 함수 호출의 수신자 또는 참조 대상에 대한 모호성을 초래하는 상황이 종종 발생합니다. 이러한 상황은 다음을 포함할 수 있습니다:
- 여러 스코프 내 트레잇이 동일한 타입에 대해 동일한 이름의 메서드를 정의하는 경우
- 자동
deref가 바람직하지 않은 경우; 예를 들어, 스마트 포인터 자체의 메서드와 포인터가 참조하는 대상의 메서드를 구별하는 경우 default()와 같이 인수를 받지 않는 메서드, 그리고size_of()와 같이 타입의 속성을 반환하는 메서드
모호성을 해결하기 위해, 프로그래머는 더 구체적인 경로, 타입 또는 트레잇을 사용하여 원하는 메서드나 함수를 참조할 수 있습니다.
예를 들어,
trait Pretty {
fn print(&self);
}
trait Ugly {
fn print(&self);
}
struct Foo;
impl Pretty for Foo {
fn print(&self) {}
}
struct Bar;
impl Pretty for Bar {
fn print(&self) {}
}
impl Ugly for Bar {
fn print(&self) {}
}
fn main() {
let f = Foo;
let b = Bar;
// `Foo` 에 대해 `print` 라고 불리는 아이템이 하나만 있기 때문에 이렇게 할 수 있습니다
f.print();
// 더 명시적이며, `Foo` 의 경우 필요하지 않습니다
Foo::print(&f);
// 간결함을 선호하지 않는다면
<Foo as Pretty>::print(&f);
// b.print(); // 오류: 여러 개의 'print' 발견됨
// Bar::print(&b); // 여전히 오류: 여러 개의 `print` 발견됨
// `print` 를 정의하는 스코프 내 아이템 때문에 필요함
<Bar as Pretty>::print(&b);
}
자세한 내용과 동기는 RFC 132 를 참조하십시오.
메서드 호출 표현식
Syntax
MethodCallExpression → Expression . PathExprSegment ( CallParams? )
메서드 호출 은 표현식( 수신자), 그 뒤에 오는 단일 점, 표현식 경로 세그먼트, 괄호로 묶인 표현식 목록으로 구성됩니다.
메서드 호출은 특정 트레잇의 연관 메서드 로 확인되며, 왼쪽의 정확한 self 타입이 알려진 경우 메서드에 정적으로 디스패치하거나, 왼쪽 표현식이 간접 트레잇 객체 인 경우 동적으로 디스패치합니다.
#![allow(unused)]
fn main() {
let pi: Result<f32, _> = "3.14".parse();
let log_pi = pi.unwrap_or(1.0).log(2.72);
assert!(1.14 < log_pi && log_pi < 1.15)
}
메서드 호출을 찾을 때, 메서드를 호출하기 위해 수신자가 자동으로 역참조되거나 차용될 수 있습니다. 호출할 수 있는 메서드가 여러 개일 수 있으므로 다른 함수보다 더 복잡한 조회 과정이 필요합니다. 다음 절차가 사용됩니다:
The first step is to build a list of candidate receiver types. Obtain these by repeatedly dereferencing the receiver expression’s type, adding each type encountered to the list, then finally attempting an array unsized coercion at the end, and adding the result type if that is successful.
그런 다음 각 후보 T 에 대해, T 바로 뒤에 목록에 &T 와 &mut T 를 추가합니다.
예를 들어, 수신자의 타입이 Box<[i32;2]> 인 경우 후보 타입은 Box<[i32;2]>, &Box<[i32;2]>, &mut Box<[i32;2]>, [i32; 2] (역참조에 의해), &[i32; 2], &mut [i32; 2], [i32] (크기 없는 강제 변환에 의해), &[i32], 마지막으로 &mut [i32] 가 됩니다.
그런 다음 각 후보 타입 T 에 대해 다음 위치에서 해당 타입의 수신자가 있는 보이는 메서드를 검색합니다:
T의 고유 메서드 (T에 직접 구현된 메서드).T에 의해 구현된 보이는 트레잇이 제공하는 메서드 중 하나.T가 타입 매개변수인 경우T의 트레잇 바운드가 제공하는 메서드가 먼저 조회됩니다. 그런 다음 스코프에 있는 나머지 모든 메서드가 조회됩니다.
Note
The lookup is done for each type in order, which can occasionally lead to surprising results. The below code will print “In trait impl!”, because
&selfmethods are looked up first, the trait method is found before the struct’s&mut selfmethod is found.struct Foo {} trait Bar { fn bar(&self); } impl Foo { fn bar(&mut self) { println!("In struct impl!") } } impl Bar for Foo { fn bar(&self) { println!("In trait impl!") } } fn main() { let mut f = Foo{}; f.bar(); }
만약 여러 가능한 후보가 생성되면 오류이며, 메서드 호출을 하려면 수신자를 적절한 수신자 타입으로 변환 해야 합니다.
이 과정은 수신자의 가변성이나 라이프타임, 또는 메서드가 unsafe 인지 여부를 고려하지 않습니다. 메서드가 조회된 후, 이러한 이유 중 하나(또는 그 이상)로 호출할 수 없는 경우 결과는 컴파일러 오류입니다.
제네릭 메서드나 트레잇이 동일하게 간주되는 등 가능한 메서드가 하나 이상 있는 단계에 도달하면 컴파일러 오류입니다. 이러한 경우 메서드 및 함수 호출을 위해 모호하지 않은 함수 호출 구문 이 필요합니다.
2021 Edition differences
Before the 2021 edition, during the search for visible methods, if the candidate receiver type is an array type, methods provided by the standard library
IntoIteratortrait are ignored.이 목적에 사용되는 에디션은 메서드 이름을 나타내는 토큰에 의해 결정됩니다.
이 특별한 경우는 나중에 제거될 수 있습니다.
Warning
For trait objects, if there is an inherent method of the same name as a trait method, it will give a compiler error when trying to call the method in a method call expression. Instead, you can call the method using disambiguating function call syntax, in which case it calls the trait method, not the inherent method. There is no way to call the inherent method. Just don’t define inherent methods on trait objects with the same name as a trait method and you’ll be fine.
필드 접근 표현식
Syntax
FieldExpression → Expression . IDENTIFIER
필드 표현식 은 구조체 또는 공용체 필드의 위치로 평가되는 장소 표현식 입니다.
피연산자가 가변 이면 필드 표현식도 가변입니다.
필드 표현식의 구문은 컨테이너 피연산자 라고 하는 표현식, 그 다음 ., 그리고 마지막으로 식별자 입니다.
필드 표현식 뒤에는 괄호로 묶인 쉼표로 구분된 표현식 목록이 올 수 없습니다. 이는 대신 메서드 호출 표현식 으로 파싱되기 때문입니다. 즉, 호출 표현식 의 함수 피연산자가 될 수 없습니다.
Note
Wrap the field expression in a parenthesized expression to use it in a call expression.
#![allow(unused)] fn main() { struct HoldsCallable<F: Fn()> { callable: F } let holds_callable = HoldsCallable { callable: || () }; // 잘못됨: 메서드 "callable" 호출로 파싱됨 // holds_callable.callable(); // 유효함 (holds_callable.callable)(); }
예:
mystruct.myfield;
foo().x;
(Struct {a: 10, b: 20}).a;
(mystruct.function_field)() // 필드 표현식을 포함하는 호출 표현식
자동 역참조
컨테이너 피연산자의 타입이 피연산자의 가변성 에 따라 Deref 또는 DerefMut 를 구현하는 경우, 필드 접근이 가능하도록 필요한 횟수만큼 자동으로 역참조 됩니다. 이 과정을 줄여서 자동 역참조(autoderef) 라고도 합니다.
차용
The fields of a struct or a reference to a struct are treated as separate entities when borrowing. If the struct does not implement Drop and is stored in a local variable, this also applies to moving out of each of its fields. This also does not apply if automatic dereferencing is done through user-defined types other than Box.
#![allow(unused)]
fn main() {
struct A { f1: String, f2: String, f3: String }
let mut x: A;
x = A {
f1: "f1".to_string(),
f2: "f2".to_string(),
f3: "f3".to_string()
};
let a: &mut String = &mut x.f1; // x.f1 가변적으로 차용됨
let b: &String = &x.f2; // x.f2 불변적으로 차용됨
let c: &String = &x.f2; // 다시 차용 가능
let d: String = x.f3; // x.f3에서 이동
}
클로저 표현식
Syntax
ClosureExpression →
async?1
move?
( || | | ClosureParameters? | )
( Expression | -> TypeNoBounds BlockExpression )
ClosureParameters → ClosureParam ( , ClosureParam )* ,?
ClosureParam → OuterAttribute* PatternNoTopAlt ( : Type )?
_클로저 표현식 (람다 표현식 또는 람다로도 알려짐)은 클로저 타입 을 정의하고 해당 타입의 값으로 평가됩니다. 클로저 표현식의 구문은 선택적인 async 키워드, 선택적인 move 키워드, 그 다음 파이프 기호(|)로 구분된 쉼표로 구분된 패턴 목록( 클로저 매개변수 라고 함, 각 패턴 뒤에는 선택적으로 : 와 타입이 올 수 있음), 그 다음 선택적인 -> 와 타입( 반환 타입 이라고 함), 그리고 마지막으로 표현식( 클로저 본문 피연산자_라고 함)입니다.
각 패턴 뒤의 선택적 타입은 패턴에 대한 타입 주석입니다.
반환 타입이 있는 경우 클로저 본문은 블록 이어야 합니다.
클로저 표현식은 매개변수 목록을 매개변수 뒤에 오는 표현식에 매핑하는 함수를 나타냅니다. let 바인딩 과 마찬가지로, 클로저 매개변수는 반박할 수 없는 패턴 이며, 타입 주석은 선택 사항이고 주어지지 않은 경우 문맥에서 유추됩니다.
각 클로저 표현식은 고유하고 익명인 타입을 가집니다.
중요한 점은 클로저 표현식이 환경을 캡처 한다는 것인데, 이는 일반적인 함수 정의 와 다른 점입니다.
move 키워드가 없으면 클로저 표현식은 환경에서 각 변수를 캡처하는 방법을 추론 하며, 공유 참조로 캡처하는 것을 선호하여 클로저 본문 내에서 언급된 모든 외부 변수를 효과적으로 차용합니다.
필요한 경우 컴파일러는 대신 가변 참조를 취하거나, 값(타입에 따라)을 환경에서 이동하거나 복사해야 한다고 추론합니다.
클로저 앞에 move 키워드를 붙여 값을 복사하거나 이동함으로써 환경을 강제로 캡처하게 할 수 있습니다. 이는 종종 클로저의 라이프타임이 'static 임을 보장하기 위해 사용됩니다.
클로저 트레잇 구현
클로저 타입이 구현하는 트레잇은 변수가 캡처되는 방식, 캡처된 변수의 타입, 그리고 async 의 존재 여부에 따라 달라집니다. 클로저가 Fn, FnMut, FnOnce 를 어떻게 그리고 언제 구현하는지에 대해서는 호출 트레잇 및 강제 변환 장을 참조하십시오. 캡처된 모든 변수의 타입도 해당 트레잇을 구현하는 경우 클로저 타입은 Send 및 Sync 를 구현합니다.
비동기 클로저
async 키워드로 표시된 클로저는 비동기 함수 와 유사한 방식으로 비동기임을 나타냅니다.
비동기 클로저를 호출하면 아무 작업도 수행되지 않는 대신 클로저 본문의 계산에 해당하는 Future 를 구현하는 값으로 평가됩니다.
#![allow(unused)]
fn main() {
async fn takes_async_callback(f: impl AsyncFn(u64)) {
f(0).await;
f(1).await;
}
async fn example() {
takes_async_callback(async |i| {
core::future::ready(i).await;
println!("{i} 완료.");
}).await;
}
}
2018 Edition differences
Async closures are only available beginning with Rust 2018.
예시
이 예제에서는 고차 함수 인수를 받는 함수 ten_times 를 정의한 다음, 이를 클로저 표현식을 인수로 호출하고, 그 뒤에 환경에서 값을 이동하는 클로저 표현식을 사용하여 호출합니다.
#![allow(unused)]
fn main() {
fn ten_times<F>(f: F) where F: Fn(i32) {
for index in 0..10 {
f(index);
}
}
ten_times(|j| println!("안녕하세요, {}", j));
// 타입 주석 포함
ten_times(|j: i32| -> () { println!("안녕하세요, {}", j) });
let word = "곤니찌와".to_owned();
ten_times(move |j| println!("{}, {}", word, j));
}
클로저 매개변수의 속성
클로저 매개변수의 속성은 일반 함수 매개변수 와 동일한 규칙 및 제한 사항을 따릅니다.
-
async한정자는 2015 에디션에서 허용되지 않습니다. ↩
루프 및 기타 중단 가능한 표현식
Syntax
LoopExpression →
LoopLabel? (
InfiniteLoopExpression
| PredicateLoopExpression
| IteratorLoopExpression
| LabelBlockExpression
)
Rust supports four loop expressions:
loop표현식 은 무한 루프를 나타냅니다.while표현식 은 조건자가 거짓이 될 때까지 반복합니다.for표현식 은 반복자에서 값을 추출하여 반복자가 빌 때까지 반복합니다.- A labeled block expression runs a loop exactly once, but allows exiting the loop early with
break.
All four types of loop support break expressions, and labels.
All except labeled block expressions support continue expressions.
Only loop and labeled block expressions support evaluation to non-trivial values.
무한 루프
Syntax
InfiniteLoopExpression → loop BlockExpression
loop 표현식은 본문 실행을 지속적으로 반복합니다: loop { println!("I live."); }.
A loop expression without an associated break expression is diverging and has type !.
연관된 break 표현식(들) 을 포함하는 loop 표현식은 종료될 수 있으며, break 표현식(들)의 값과 호환되는 타입을 가져야 합니다.
조건자 루프
Syntax
PredicateLoopExpression → while Conditions BlockExpression
A while loop expression allows repeating the evaluation of a block while a set of conditions remain true.
Condition operands must be either an Expression with a boolean type or a conditional let match. If all of the condition operands evaluate to true and all of the let patterns successfully match their scrutinees, then the loop body block executes.
After the loop body successfully executes, the condition operands are re-evaluated to determine if the body should be executed again.
If any condition operand evaluates to false or any let pattern does not match its scrutinee, the body is not executed and execution continues after the while expression.
A while expression evaluates to ().
예:
#![allow(unused)]
fn main() {
let mut i = 0;
while i < 10 {
println!("hello");
i = i + 1;
}
}
while let patterns
let patterns in a while condition allow binding new variables into scope when the pattern matches successfully. The following examples illustrate bindings using let patterns:
#![allow(unused)]
fn main() {
let mut x = vec![1, 2, 3];
while let Some(y) = x.pop() {
println!("y = {}", y);
}
while let _ = 5 {
println!("반박할 수 없는 패턴은 항상 참입니다");
break;
}
}
while let 루프는 다음과 같이 match 표현식 을 포함하는 loop 표현식과 동일합니다.
'label: while let PATS = EXPR {
/* 루프 본문 */
}
다음과 동일합니다.
'label: loop {
match EXPR {
PATS => { /* 루프 본문 */ },
_ => break,
}
}
여러 패턴을 | 연산자로 지정할 수 있습니다. 이는 match 표현식의 | 와 동일한 의미를 갖습니다:
#![allow(unused)]
fn main() {
let mut vals = vec![2, 3, 1, 2, 2];
while let Some(v @ 1) | Some(v @ 2) = vals.pop() {
// 2, 2, 그 다음 1을 출력합니다
println!("{}", v);
}
}
while condition chains
Multiple condition operands can be separated with &&. These have the same semantics and restrictions as if condition chains.
The following is an example of chaining multiple expressions, mixing let bindings and boolean expressions, and with expressions able to reference pattern bindings from previous expressions:
fn main() {
let outer_opt = Some(Some(1i32));
while let Some(inner_opt) = outer_opt
&& let Some(number) = inner_opt
&& number == 1
{
println!("Peek a boo");
break;
}
}
반복자 루프
Syntax
IteratorLoopExpression →
for Pattern in Expressionexcept StructExpression BlockExpression
for 표현식은 std::iter::IntoIterator 구현에서 제공하는 요소를 반복하기 위한 구문 구조입니다.
반복자가 값을 산출하면 해당 값은 반박할 수 없는 패턴과 매칭되고 루프 본문이 실행된 다음 제어가 for 루프의 헤드로 돌아갑니다. 반복자가 비어 있으면 for 표현식이 완료됩니다.
배열의 내용에 대한 for 루프의 예:
#![allow(unused)]
fn main() {
let v = &["사과", "케이크", "커피"];
for text in v {
println!("저는 {}를 좋아합니다.", text);
}
}
일련의 정수에 대한 for 루프의 예:
#![allow(unused)]
fn main() {
let mut sum = 0;
for n in 1..11 {
sum += n;
}
assert_eq!(sum, 55);
}
for 루프는 다음과 같이 match 표현식 을 포함하는 loop 표현식과 동일합니다:
'label: for PATTERN in iter_expr {
/* 루프 본문 */
}
다음과 동일합니다.
{
let result = match IntoIterator::into_iter(iter_expr) {
mut iter => 'label: loop {
let mut next;
match Iterator::next(&mut iter) {
Option::Some(val) => next = val,
Option::None => break,
};
let PATTERN = next;
let () = { /* 루프 본문 */ };
},
};
result
}
여기서 IntoIterator, Iterator, Option 은 현재 스코프에서 해당 이름이 무엇으로 확인되든 상관없이 항상 표준 라이브러리 항목입니다.
변수 이름 next, iter, val 은 설명용일 뿐이며, 실제로 사용자가 입력할 수 있는 이름은 아닙니다.
Note
The outer
matchis used to ensure that any temporary values initer_exprdon’t get dropped before the loop is finished.nextis declared before being assigned because it results in types being inferred correctly more often.
루프 레이블
Syntax
LoopLabel → LIFETIME_OR_LABEL :
루프 표현식은 선택적으로 레이블 을 가질 수 있습니다. 레이블은 'foo: loop { break 'foo; }, 'bar: while false {}, 'humbug: for _ in 0..0 {} 과 같이 루프 표현식 앞에 라이프타임으로 작성됩니다.
레이블이 있는 경우, 이 루프 내에 중첩된 레이블이 있는 break 및 continue 표현식은 이 루프를 빠져나가거나 제어를 루프 헤드로 반환할 수 있습니다. break 표현식 및 continue 표현식 을 참조하십시오.
레이블은 지역 변수의 위생(hygiene) 및 섀도잉 규칙을 따릅니다. 예를 들어, 이 코드는 “outer loop“를 출력합니다:
#![allow(unused)]
fn main() {
'a: loop {
'a: loop {
break 'a;
}
print!("outer loop");
break 'a;
}
}
'_ 는 유효한 루프 레이블이 아닙니다.
break 표현식
Syntax
BreakExpression → break LIFETIME_OR_LABEL? Expression?
break 를 만나면 연관된 루프 본문의 실행이 즉시 종료됩니다. 예를 들면:
#![allow(unused)]
fn main() {
let mut last = 0;
for x in 1..100 {
if x > 12 {
break;
}
last = x;
}
assert_eq!(last, 12);
}
A break expression is diverging and has a type of !.
break 표현식은 일반적으로 break 표현식을 감싸는 가장 안쪽의 loop, for 또는 while 루프와 연관되지만, 레이블 을 사용하여 영향을 받는 둘러싸는 루프를 지정할 수 있습니다. 예:
#![allow(unused)]
fn main() {
'outer: loop {
while true {
break 'outer;
}
}
}
break 표현식은 루프 본문에서만 허용되며, break, break 'label 또는 (아래 참조) break EXPR 또는 break 'label EXPR 중 하나의 형식을 가집니다.
In a loop with break expressions or a labeled block expression, a break without an expression is equivalent to break ().
Labeled block expressions
Syntax
LabelBlockExpression → BlockExpression
Labeled block expressions are exactly like block expressions, except that they allow using break expressions within the block.
Unlike loops, break expressions within a labeled block expression must have a label (i.e. the label is not optional).
Similarly, labeled block expressions must begin with a label.
#![allow(unused)]
fn main() {
fn do_thing() {}
fn condition_not_met() -> bool { true }
fn do_next_thing() {}
fn do_last_thing() {}
let result = 'block: {
do_thing();
if condition_not_met() {
break 'block 1;
}
do_next_thing();
if condition_not_met() {
break 'block 2;
}
do_last_thing();
3
};
}
The type of a labeled block expression is the least upper bound of all of the break operands and the final operand. If the final operand is omitted, the type of the final operand defaults to the unit type, unless the block diverges, in which case it is the never type.
Example
#![allow(unused)] fn main() { fn example(condition: bool) { let s = String::from("owned"); let _: &str = 'block: { if condition { break 'block &s; // &String coerced to &str via Deref } break 'block "literal"; // &'static str coerced to &str }; } }
continue 표현식
Syntax
ContinueExpression → continue LIFETIME_OR_LABEL?
continue 를 만나면 연관된 루프 본문의 현재 반복이 즉시 종료되고, 제어가 루프 헤드 로 반환됩니다.
A continue expression is diverging and has a type of !.
In the case of a while loop, the head is the conditional operands controlling the loop.
for 루프의 경우, 헤드는 루프를 제어하는 호출 표현식입니다.
break 와 마찬가지로, continue 는 일반적으로 가장 안쪽의 둘러싸는 루프와 연관되지만, continue 'label 을 사용하여 영향을 받는 루프를 지정할 수 있습니다.
continue 표현식은 루프 본문에서만 허용됩니다.
break 와 루프 값
When associated with a loop, a break expression may be used to return a value from that loop, via one of the forms break EXPR or break 'label EXPR, where EXPR is an expression whose result is returned from the loop. For example:
#![allow(unused)]
fn main() {
let (mut a, mut b) = (1, 1);
let result = loop {
if b > 10 {
break b;
}
let c = a + b;
a = b;
b = c;
};
// 피보나치 수열에서 10을 넘는 첫 번째 수:
assert_eq!(result, 13);
}
The type of a loop with associated break expressions is the least upper bound of all of the break operands.
Example
#![allow(unused)] fn main() { fn example(condition: bool) { let s = String::from("owned"); let _: &str = loop { if condition { break &s; // &String coerced to &str via Deref } break "literal"; // &'static str coerced to &str }; } }
A loop with associated break expressions does not diverge if any of the break operands do not diverge. If all of the break operands diverge, then the loop expression also diverges.
Example
#![allow(unused)] fn main() { fn diverging_loop_with_break(condition: bool) -> ! { // This loop is diverging because all `break` operands are diverging. loop { if condition { break loop {}; } else { break panic!(); } } } }#![allow(unused)] fn main() { fn loop_with_non_diverging_break(condition: bool) -> ! { // The type of this loop is i32 even though one of the breaks is // diverging. loop { if condition { break loop {}; } else { break 123i32; } } // ERROR: expected `!`, found `i32` } }
범위 표현식
Syntax
RangeExpression →
RangeExpr
| RangeFromExpr
| RangeToExpr
| RangeFullExpr
| RangeInclusiveExpr
| RangeToInclusiveExpr
RangeExpr → Expression .. Expression
RangeFromExpr → Expression ..
RangeToExpr → .. Expression
RangeFullExpr → ..
.. 및 ..= 연산자는 다음 표에 따라 std::ops::Range(또는 core::ops::Range) 변형 중 하나의 객체를 생성합니다:
| 생성 | 구문 | 유형 | 범위 |
|---|---|---|---|
| RangeExpr | start..end | std::ops::Range | start ≤ x < end |
| RangeFromExpr | start.. | std::ops::RangeFrom | start ≤ x |
| RangeToExpr | ..end | std::ops::RangeTo | x < end |
| RangeFullExpr | .. | std::ops::RangeFull | - |
| RangeInclusiveExpr | start..=end | std::ops::RangeInclusive | start ≤ x ≤ end |
| RangeToInclusiveExpr | ..=end | std::ops::RangeToInclusive | x ≤ end |
예:
#![allow(unused)]
fn main() {
1..2; // std::ops::Range
3..; // std::ops::RangeFrom
..4; // std::ops::RangeTo
..; // std::ops::RangeFull
5..=6; // std::ops::RangeInclusive
..=7; // std::ops::RangeToInclusive
}
다음 표현식들은 동일합니다.
#![allow(unused)]
fn main() {
let x = std::ops::Range {start: 0, end: 10};
let y = 0..10;
assert_eq!(x, y);
}
범위는 for 루프에서 사용할 수 있습니다:
#![allow(unused)]
fn main() {
for i in 1..11 {
println!("{}", i);
}
}
if 표현식
Syntax
IfExpression →
if Conditions BlockExpression
( else ( BlockExpression | IfExpression ) )?
Conditions →
Expressionexcept StructExpression
| LetChain
LetChain → LetChainCondition ( && LetChainCondition )*
LetChainCondition →
Expressionexcept ExcludedConditions
| OuterAttribute* let Pattern = Scrutineeexcept ExcludedConditions
ExcludedConditions →
StructExpression
| LazyBooleanExpression
| RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| AssignmentExpression
| CompoundAssignmentExpression
The syntax of an if expression is a sequence of one or more condition operands separated by &&, followed by a consequent block, any number of else if conditions and blocks, and an optional trailing else block.
Condition operands must be either an Expression with a boolean type or a conditional let match.
If all of the condition operands evaluate to true and all of the let patterns successfully match their scrutinees, the consequent block is executed and any subsequent else if or else block is skipped.
If any condition operand evaluates to false or any let pattern does not match its scrutinee, the consequent block is skipped and any subsequent else if condition is evaluated.
모든 if 및 else if 조건이 false 로 평가되면 else 블록이 실행됩니다.
An if expression evaluates to the same value as the executed block, or () if no block is evaluated.
if 표현식은 모든 상황에서 동일한 타입을 가져야 합니다.
#![allow(unused)]
fn main() {
let x = 3;
if x == 4 {
println!("x는 4");
} else if x == 3 {
println!("x는 3");
} else {
println!("x는 다른 것");
}
// `if` can be used as an expression.
let y = if 12 * 15 > 150 {
"더 큼"
} else {
"더 작음"
};
assert_eq!(y, "더 큼");
}
An if expression diverges if either the condition expression diverges or if all arms diverge.
#![allow(unused)]
fn main() {
fn diverging_condition() -> ! {
// Diverges because the condition expression diverges
if loop {} {
()
} else {
()
};
// The semicolon above is important: The type of the `if` expression is
// `()`, despite being diverging. When the final body expression is
// elided, the type of the body is inferred to ! because the function body
// diverges. Without the semicolon, the `if` would be the tail expression
// with type `()`, which would fail to match the return type `!`.
}
fn diverging_arms() -> ! {
// Diverges because all arms diverge
if true {
loop {}
} else {
loop {}
}
}
}
if let patterns
let patterns in an if condition allow binding new variables into scope when the pattern matches successfully.
The following examples illustrate bindings using let patterns:
#![allow(unused)]
fn main() {
let dish = ("햄", "달걀");
// This body will be skipped because the pattern is refuted.
if let ("베이컨", b) = dish {
println!("베이컨은 {}와(과) 함께 제공됩니다", b);
} else {
// 대신 이 블록이 평가됩니다.
println!("베이컨은 제공되지 않습니다");
}
// This body will execute.
if let ("햄", b) = dish {
println!("햄은 {}와(과) 함께 제공됩니다", b);
}
if let _ = 5 {
println!("반박할 수 없는 패턴은 항상 참입니다");
}
}
Multiple patterns may be specified with the | operator. This has the same semantics as with | in match expressions:
#![allow(unused)]
fn main() {
enum E {
X(u8),
Y(u8),
Z(u8),
}
let v = E::Y(12);
if let E::X(n) | E::Y(n) = v {
assert_eq!(n, 12);
}
}
Chains of conditions
Multiple condition operands can be separated with &&.
Similar to a && LazyBooleanExpression, each operand is evaluated from left-to-right until an operand evaluates as false or a let match fails, in which case the subsequent operands are not evaluated.
The bindings of each pattern are put into scope to be available for the next condition operand and the consequent block.
The following is an example of chaining multiple expressions, mixing let bindings and boolean expressions, and with expressions able to reference pattern bindings from previous expressions:
#![allow(unused)]
fn main() {
fn single() {
let outer_opt = Some(Some(1i32));
if let Some(inner_opt) = outer_opt
&& let Some(number) = inner_opt
&& number == 1
{
println!("Peek a boo");
}
}
}
The above is equivalent to the following without using chains of conditions:
#![allow(unused)]
fn main() {
fn nested() {
let outer_opt = Some(Some(1i32));
if let Some(inner_opt) = outer_opt {
if let Some(number) = inner_opt {
if number == 1 {
println!("Peek a boo");
}
}
}
}
}
If any condition operand is a let pattern, then none of the condition operands can be a || lazy boolean operator expression due to ambiguity and precedence with the let scrutinee. If a || expression is needed, then parentheses can be used. For example:
#![allow(unused)]
fn main() {
let foo = Some(123);
let condition1 = true;
let condition2 = false;
// Parentheses are required here.
if let Some(x) = foo && (condition1 || condition2) { /*...*/ }
}
2024 Edition differences
Before the 2024 edition, let chains are not supported. That is, the LetChain grammar is not allowed in an
ifexpression.
match 표현식
Syntax
MatchExpression →
match Scrutinee {
InnerAttribute*
MatchArms?
}
Scrutinee → Expressionexcept StructExpression
MatchArms →
( MatchArm => ( ExpressionWithoutBlock , | ExpressionWithBlock ,? ) )*
MatchArm => Expression ,?
MatchArm → OuterAttribute* Pattern MatchArmGuard?
MatchArmGuard → if MatchConditions
MatchConditions →
MatchGuardChain
| Expression
MatchGuardChain → MatchGuardCondition ( && MatchGuardCondition )*
MatchGuardCondition →
Expressionexcept ExcludedMatchConditions
| OuterAttribute* let Pattern = MatchGuardScrutinee
MatchGuardScrutinee → Expressionexcept ExcludedMatchConditions
ExcludedMatchConditions →
LazyBooleanExpression
| RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| AssignmentExpression
| CompoundAssignmentExpression
match 표현식 은 패턴에 따라 분기합니다. 발생하는 매칭의 정확한 형태는 패턴 에 따라 다릅니다.
match 표현식에는 패턴과 비교할 값인 검사 대상 표현식 이 있습니다.
검사 대상 표현식과 패턴은 동일한 타입을 가져야 합니다.
match 는 검사 대상 표현식이 장소 표현식인지 값 표현식인지 에 따라 다르게 동작합니다.
검사 대상 표현식이 값 표현식 인 경우, 먼저 임시 위치로 평가되고, 결과 값은 일치하는 항목을 찾을 때까지 분기의 패턴과 순차적으로 비교됩니다. 일치하는 패턴이 있는 첫 번째 분기가 match 의 분기 대상으로 선택되고, 패턴에 의해 바인딩된 모든 변수는 분기 블록의 지역 변수에 할당되며, 제어가 블록으로 들어갑니다.
검사 대상 표현식이 장소 표현식 인 경우, 매치는 임시 위치를 할당하지 않습니다. 그러나 값에 의한 바인딩은 메모리 위치에서 복사하거나 이동할 수 있습니다. 가능한 경우 장소 표현식에 대해 매칭하는 것이 바람직한데, 이러한 매칭의 라이프타임은 매치 내부로 제한되지 않고 장소 표현식의 라이프타임을 상속하기 때문입니다.
match 표현식의 예:
#![allow(unused)]
fn main() {
let x = 1;
match x {
1 => println!("하나"),
2 => println!("둘"),
3 => println!("셋"),
4 => println!("넷"),
5 => println!("다섯"),
_ => println!("그 외의 것"),
}
}
패턴 내에 바인딩된 변수의 스코프는 매치 가드와 분기의 표현식으로 제한됩니다.
바인딩 모드(이동, 복사 또는 참조)는 패턴에 따라 다릅니다.
여러 매치 패턴을 | 연산자로 결합할 수 있습니다. 성공적인 매치를 찾을 때까지 각 패턴을 왼쪽에서 오른쪽 순서로 테스트합니다.
#![allow(unused)]
fn main() {
let x = 9;
let message = match x {
0 | 1 => "많지 않음",
2 ..= 9 => "약간",
_ => "많음"
};
assert_eq!(message, "약간");
// 패턴 매칭 순서 예시.
struct S(i32, i32);
match S(1, 2) {
S(z @ 1, _) | S(_, z @ 2) => assert_eq!(z, 1),
_ => panic!(),
}
}
Note
The
2..=9is a Range Pattern, not a Range Expression. Thus, only those types of ranges supported by range patterns can be used in match arms.
각 | 로 구분된 패턴의 모든 바인딩은 분기의 모든 패턴에 나타나야 합니다.
동일한 이름의 모든 바인딩은 동일한 타입과 동일한 바인딩 모드를 가져야 합니다.
The type of the overall match expression is the least upper bound of the individual match arms.
If there are no match arms, then the match expression is diverging and the type is !.
Example
#![allow(unused)] fn main() { fn make<T>() -> T { loop {} } enum Empty {} fn diverging_match_no_arms() -> ! { let e: Empty = make(); match e {} } }
If either the scrutinee expression or all of the match arms diverge, then the entire match expression also diverges.
매치 가드
매치 분기는 케이스 일치 기준을 더 구체화하기 위해 매치 가드 를 허용할 수 있습니다.
Pattern guards appear after the pattern following the if keyword and consist of an Expression with a boolean type or a conditional let match.
When the pattern matches successfully, the pattern guard is executed. If all of the guard condition operands evaluate to true and all of the let patterns successfully match their scrutinees, the match arm is successfully matched against and the arm body is executed.
그렇지 않으면, 동일한 분기의 | 연산자를 사용한 다른 일치 항목을 포함하여 다음 패턴이 테스트됩니다.
#![allow(unused)]
fn main() {
let maybe_digit = Some(0);
fn process_digit(i: i32) { }
fn process_other(i: i32) { }
let message = match maybe_digit {
Some(x) if x < 10 => process_digit(x),
Some(x) => process_other(x),
None => panic!(),
};
}
Note
Multiple matches using the
|operator can cause the pattern guard and the side effects it has to execute multiple times. For example:#![allow(unused)] fn main() { use std::cell::Cell; let i : Cell<i32> = Cell::new(0); match 1 { 1 | _ if { i.set(i.get() + 1); false } => {} _ => {} } assert_eq!(i.get(), 2); }
패턴 가드는 뒤따르는 패턴 내에 바인딩된 변수를 참조할 수 있습니다.
가드를 평가하기 전에, 변수가 일치하는 검사 대상의 부분에 대한 공유 참조가 취해집니다. 가드를 평가하는 동안 변수에 접근할 때 이 공유 참조가 사용됩니다.
Only when the guard evaluates successfully is the value moved, or copied, from the scrutinee into the variable. This allows shared borrows to be used inside guards without moving out of the scrutinee in case guard fails to match.
게다가, 가드를 평가하는 동안 공유 참조를 유지함으로써 가드 내부에서의 변경도 방지됩니다.
Guards can use let patterns to conditionally match a scrutinee and to bind new variables into scope when the pattern matches successfully.
Example
In this example, the guard condition
let Some(first_char) = name.chars().next()is evaluated. If theletpattern successfully matches (i.e. the string has at least one character), the arm’s body is executed. Otherwise, pattern matching continues to the next arm.The
letpattern creates a new binding (first_char), which can be used alongside the original pattern bindings (name) in the arm’s body.#![allow(unused)] fn main() { enum Command { Run(String), Stop, } let cmd = Command::Run("example".to_string()); match cmd { Command::Run(name) if let Some(first_char) = name.chars().next() => { // Both `name` and `first_char` are available here println!("Running: {name} (starts with '{first_char}')"); } Command::Run(name) => { println!("{name} is empty"); } _ => {} } }
Match guard chains
Multiple guard condition operands can be separated with &&.
Example
#![allow(unused)] fn main() { let foo = Some([123]); let already_checked = false; match foo { Some(xs) if let [single] = xs && !already_checked => { dbg!(single); } _ => {} } }
Similar to a && LazyBooleanExpression, each operand is evaluated from left-to-right until an operand evaluates as false or a let match fails, in which case the subsequent operands are not evaluated.
The bindings of each let pattern are put into scope to be available for the next condition operand and the match arm body.
If any guard condition operand is a let pattern, then none of the condition operands can be a || lazy boolean operator expression due to ambiguity and precedence with the let scrutinee.
Example
If a
||expression is needed, then parentheses can be used. For example:#![allow(unused)] fn main() { let foo = Some([123]); match foo { // Parentheses are required here. Some(xs) if let [x] = xs && (x < -100 || x > 20) => {} _ => {} } }
매치 분기의 속성
매치 분기에 외부 속성이 허용됩니다. 매치 분기에서 의미가 있는 속성은 cfg 와 린트 검사 속성 뿐입니다.
내부 속성 은 블록 표현식의 속성 과 동일한 표현식 문맥에서 매치 표현식의 여는 중괄호 바로 뒤에 허용됩니다.
return 표현식
Syntax
ReturnExpression → return Expression?
리턴 표현식은 키워드 return 으로 표시됩니다.
return 표현식을 평가하면 인수를 현재 함수 호출에 지정된 출력 위치로 이동하고, 현재 함수 활성화 프레임을 파괴하며, 제어를 호출자 프레임으로 전달합니다.
A return expression is diverging and has a type of !.
return 표현식의 예:
#![allow(unused)]
fn main() {
fn max(a: i32, b: i32) -> i32 {
if a > b {
return a;
}
return b;
}
}
await 표현식
Syntax
AwaitExpression → Expression . await
await 표현식은 std::future::IntoFuture 구현에서 제공하는 계산을 주어진 퓨처가 값을 생성할 준비가 될 때까지 일시 중단하기 위한 구문 구조입니다.
await 표현식의 구문은 IntoFuture 트레잇을 구현하는 타입의 표현식(_ 퓨처 피연산자_라고 함), 그 다음 토큰 ., 그리고 await 키워드입니다.
Await 표현식은 async fn, async 클로저 또는 async 블록 과 같은 비동기 컨텍스트 내에서만 유효합니다.
더 구체적으로, await 표현식은 다음과 같은 효과를 가집니다.
- 퓨처 피연산자에서
IntoFuture::into_future를 호출하여 퓨처를 생성합니다. - 퓨처를 퓨처
tmp로 평가합니다; Pin::new_unchecked를 사용하여tmp를 고정(pin)합니다;- 그런 다음 이 고정된 퓨처는
Future::poll메서드를 호출하고 현재 태스크 컨텍스트 를 전달하여 폴링됩니다; - If the call to
pollreturnsPoll::Pending, then the future returnsPoll::Pending, suspending its state so that, when the surrounding async context is re-polled, execution returns to step 3; - 그렇지 않으면
poll호출은Poll::Ready를 반환했어야 하며, 이 경우Poll::Ready변형에 포함된 값이await표현식 자체의 결과로 사용됩니다.
2018 Edition differences
Await expressions are only available beginning with Rust 2018.
태스크 컨텍스트
태스크 컨텍스트는 비동기 컨텍스트 자체가 폴링될 때 현재 비동기 컨텍스트 에 제공된 Context 를 나타냅니다. await 표현식은 비동기 컨텍스트에서만 유효하기 때문에, 사용 가능한 태스크 컨텍스트가 있어야 합니다.
대략적인 탈설탕
사실상, await 표현식은 다음의 비규범적 탈설탕과 대략적으로 동일합니다:
match operand.into_future() {
mut pinned => loop {
let mut pin = unsafe { Pin::new_unchecked(&mut pinned) };
match Pin::future::poll(Pin::borrow(&mut pin), &mut current_context) {
Poll::Ready(r) => break r,
Poll::Pending => yield Poll::Pending,
}
}
}
여기서 yield 의사 코드는 Poll::Pending 을 반환하고, 다시 호출될 때 해당 지점에서 실행을 재개합니다. 변수 current_context 는 비동기 환경에서 가져온 컨텍스트를 나타냅니다.
_ 표현식
Syntax
UnderscoreExpression → _
_ 기호로 표시되는 밑줄 표현식은 구조 분해 할당에서 플레이스홀더를 나타내는 데 사용됩니다.
이들은 할당의 왼쪽에만 나타날 수 있습니다.
이것은 와일드카드 패턴 과는 구별된다는 점에 유의하십시오.
_ 표현식의 예:
#![allow(unused)]
fn main() {
let p = (1, 2);
let mut a = 0;
(_, a) = p;
struct Position {
x: u32,
y: u32,
}
Position { x: a, y: _ } = Position{ x: 2, y: 3 };
// 사용되지 않는 결과, 의도를 선언하고 경고를 제거하기 위해 `_` 에 할당함
_ = 2 + 2;
// unused_must_use 경고 발생
// 2 + 2;
// let 바인딩에서 와일드카드 패턴을 사용하는 동일한 기법
let _ = 2 + 2;
}
패턴
Syntax
Pattern → |? PatternNoTopAlt ( | PatternNoTopAlt )*
PatternNoTopAlt →
PatternWithoutRange
| RangePattern
PatternWithoutRange →
LiteralPattern
| IdentifierPattern
| WildcardPattern
| RestPattern
| ReferencePattern
| StructPattern
| TupleStructPattern
| TuplePattern
| GroupedPattern
| SlicePattern
| PathPattern
| MacroInvocation
패턴은 값을 구조에 대해 매칭하고, 선택적으로 구조 내부의 값에 변수를 바인딩하는 데 사용됩니다. 또한 변수 선언과 함수 및 클로저의 매개변수에도 사용됩니다.
다음 예제의 패턴은 네 가지 작업을 수행합니다:
person의car필드가 무언가로 채워져 있는지 테스트합니다.- 사람의
age필드가 13에서 19 사이인지 테스트하고, 그 값을person_age변수에 바인딩합니다. name필드에 대한 참조를person_name변수에 바인딩합니다.person의 나머지 필드를 무시합니다. 나머지 필드는 어떤 값이든 가질 수 있으며 어떤 변수에도 바인딩되지 않습니다.
#![allow(unused)]
fn main() {
struct Car;
struct Computer;
struct Person {
name: String,
car: Option<Car>,
computer: Option<Computer>,
age: u8,
}
let person = Person {
name: String::from("John"),
car: Some(Car),
computer: None,
age: 15,
};
if let
Person {
car: Some(_),
age: person_age @ 13..=19,
name: ref person_name,
..
} = person
{
println!("{}는 차가 있고 {}살입니다.", person_name, person_age);
}
}
패턴은 다음에서 사용됩니다:
구조 분해
패턴은 구조체, 열거형, 튜플 을 구조 분해 하는 데 사용될 수 있습니다. 구조 분해는 값을 구성 요소로 나눕니다. 사용되는 구문은 해당 값을 생성할 때와 거의 동일합니다.
In a pattern whose scrutinee expression has a struct, enum or tuple type, a wildcard pattern (_) stands in for a single data field, whereas an et cetera or rest pattern (..) stands in for all the remaining fields of a particular variant.
이름이 있는(번호가 아닌) 필드를 가진 데이터 구조를 구조 분해할 때, fieldname: fieldname 의 단축형으로 fieldname 을 작성할 수 있습니다.
#![allow(unused)]
fn main() {
enum Message {
Quit,
WriteString(String),
Move { x: i32, y: i32 },
ChangeColor(u8, u8, u8),
}
let message = Message::Quit;
match message {
Message::Quit => println!("Quit"),
Message::WriteString(write) => println!("{}", &write),
Message::Move{ x, y: 0 } => println!("가로로 {}만큼 이동", x),
Message::Move{ .. } => println!("다른 이동"),
Message::ChangeColor { 0: red, 1: green, 2: _ } => {
println!("색상 변경, 빨강: {}, 초록: {}", red, green);
}
};
}
반박 가능성
패턴이 비교 대상 값과 일치하지 않을 가능성이 있는 경우를 반박 가능(refutable) 하다고 합니다. 반면에 반박 불가능(irrefutable) 한 패턴은 비교 대상 값과 항상 일치합니다. 예시:
#![allow(unused)]
fn main() {
let (x, y) = (1, 2); // "(x, y)"는 반박 불가능한 패턴입니다
if let (a, 3) = (1, 2) { // "(a, 3)"은 반박 가능하며, 일치하지 않을 수 있습니다
panic!("여기에 도달해서는 안 됩니다");
} else if let (a, 4) = (3, 4) { // "(a, 4)"는 반박 가능하며, 일치하게 됩니다
println!("({}, 4)와 일치함", a);
}
}
리터럴 패턴
Syntax
LiteralPattern → -? LiteralExpression
Literal patterns match exactly the same value as what is created by the literal. Since negative numbers are not literals, literals in patterns may be prefixed by an optional minus sign, which acts like the negation operator.
Warning
C string and raw C string literals are accepted in literal patterns, but
&CStrdoesn’t implement structural equality (#[derive(Eq, PartialEq)]) and therefore any suchmatchon a&CStrwill be rejected with a type error.
리터럴 패턴은 항상 반박 가능합니다.
예:
#![allow(unused)]
fn main() {
for i in -2..5 {
match i {
-1 => println!("마이너스 1입니다"),
1 => println!("1입니다"),
2|4 => println!("2 또는 4입니다"),
_ => println!("어떠한 매치 암(arm)과도 일치하지 않았습니다"),
}
}
}
식별자 패턴
Syntax
IdentifierPattern → ref? mut? IDENTIFIER ( @ PatternNoTopAlt )?
Identifier patterns bind the value they match to a variable in the value namespace.
식별자는 패턴 내에서 유일해야 합니다.
변수는 스코프 내의 동일한 이름을 가진 다른 변수를 섀도잉(shadowing)합니다. 새 바인딩의 스코프 는 패턴이 사용되는 컨텍스트(let 바인딩 또는 match 암 등)에 따라 달라집니다.
식별자만으로 구성된(선택적으로 mut 가 붙은) 패턴은 모든 값과 매치되며 그 값을 해당 식별자에 바인딩합니다. 이는 변수 선언과 함수 및 클로저의 매개변수에서 가장 흔히 사용되는 패턴입니다.
#![allow(unused)]
fn main() {
let mut variable = 10;
fn sum(x: i32, y: i32) -> i32 {
x + y
}
}
패턴의 매치된 값을 변수에 바인딩하려면 variable @ 서브패턴 구문을 사용합니다. 예를 들어, 다음은 값 2를 e 에 바인딩합니다(전체 범위가 아니라, 여기서 범위는 범위 서브패턴입니다).
#![allow(unused)]
fn main() {
let x = 2;
match x {
e @ 1 ..= 5 => println!("범위 요소 {}를 얻음", e),
_ => println!("아무거나"),
}
}
기본적으로 식별자 패턴은 매치된 값이 Copy 를 구현하는지 여부에 따라 매치된 값을 복사하거나 이동하여 변수에 바인딩합니다.
ref 키워드를 사용하여 참조로 바인딩하거나, ref mut 를 사용하여 가변 참조로 바인딩하도록 변경할 수 있습니다. 예시:
#![allow(unused)]
fn main() {
let a = Some(10);
match a {
None => (),
Some(value) => (),
}
match a {
None => (),
Some(ref value) => (),
}
}
첫 번째 match 표현식에서 값은 복사(또는 이동)됩니다. 두 번째 match에서 동일한 메모리 위치에 대한 참조가 변수 값에 바인딩됩니다. 구조 분해 서브패턴에서는 & 연산자를 값의 필드에 적용할 수 없기 때문에 이 구문이 필요합니다. 예를 들어, 다음은 유효하지 않습니다:
#![allow(unused)]
fn main() {
struct Person {
name: String,
age: u8,
}
let value = Person { name: String::from("John"), age: 23 };
if let Person { name: &person_name, age: 18..=150 } = value { }
}
이를 유효하게 만들려면 다음과 같이 작성하십시오:
#![allow(unused)]
fn main() {
struct Person {
name: String,
age: u8,
}
let value = Person { name: String::from("John"), age: 23 };
if let Person { name: ref person_name, age: 18..=150 } = value { }
}
따라서 ref 는 매치 대상이 아닙니다. 이것의 목적은 오로지 매치된 바인딩을 복사하거나 이동하는 대신 참조로 만드는 것입니다.
경로 패턴 은 식별자 패턴보다 우선순위가 높습니다.
Note
When a pattern is a single-segment identifier, the grammar is ambiguous whether it means an IdentifierPattern or a PathPattern. This ambiguity can only be resolved after name resolution.
#![allow(unused)] fn main() { const EXPECTED_VALUE: u8 = 42; // ^^^^^^^^^^^^^^ That this constant is in scope affects how the // patterns below are treated. fn check_value(x: u8) -> Result<u8, u8> { match x { EXPECTED_VALUE => Ok(x), // ^^^^^^^^^^^^^^ Parsed as a `PathPattern` that resolves to // the constant `42`. other_value => Err(x), // ^^^^^^^^^^^ Parsed as an `IdentifierPattern`. } } // If `EXPECTED_VALUE` were treated as an `IdentifierPattern` above, // that pattern would always match, making the function always return // `Ok(_) regardless of the input. assert_eq!(check_value(42), Ok(42)); assert_eq!(check_value(43), Err(43)); }
ref 또는 ref mut 가 지정되고 식별자가 상수를 섀도잉하면 오류입니다.
@ 서브패턴이 반박 불가능하거나 서브패턴이 지정되지 않은 경우 식별자 패턴은 반박 불가능합니다.
바인딩 모드
더 나은 편의성을 제공하기 위해, 패턴은 값에 참조를 더 쉽게 바인딩할 수 있도록 서로 다른 바인딩 모드 에서 작동합니다. 참조 값이 비참조 패턴에 의해 매치될 때, 자동으로 ref 또는 ref mut 바인딩으로 취급됩니다. 예시:
#![allow(unused)]
fn main() {
let x: &Option<i32> = &Some(3);
if let Some(y) = x {
// y는 `ref y` 로 변환되었으며 타입은 &i32입니다
}
}
_비참조 패턴 _에는 바인딩, 와일드카드 패턴 (_), 참조 타입의 const 패턴, 참조 패턴 을 제외한 모든 패턴이 포함됩니다.
바인딩 패턴에 ref, ref mut, 또는 mut 가 명시적으로 없는 경우, 변수가 바인딩되는 방식을 결정하기 위해 기본 바인딩 모드 를 사용합니다.
기본 바인딩 모드는 이동 시맨틱을 사용하는 “move” 모드에서 시작합니다.
패턴을 매치할 때, 컴파일러는 패턴의 바깥쪽에서 시작하여 안쪽으로 진행합니다.
비참조 패턴을 사용하여 참조를 매치할 때마다, 자동으로 값을 역참조하고 기본 바인딩 모드를 업데이트합니다.
참조는 기본 바인딩 모드를 ref 로 설정합니다.
가변 참조는 모드가 이미 ref 가 아닌 한 ref mut 로 설정하며, 이미 ref 인 경우에는 ref 로 유지됩니다.
자동으로 역참조된 값이 여전히 참조인 경우, 다시 역참조되며 이 과정이 반복됩니다.
바인딩 패턴은 기본 바인딩 모드가 “move“일 때만 ref 또는 ref mut 바인딩 모드를 명시적으로 지정하거나 mut 로 가변성을 지정할 수 있습니다. 예를 들어, 다음은 허용되지 않습니다:
#![allow(unused)]
fn main() {
let [mut x] = &[()]; //~ 오류
let [ref x] = &[()]; //~ 오류
let [ref mut x] = &mut [()]; //~ 오류
}
2024 Edition differences
Before the 2024 edition, bindings could explicitly specify a
reforref mutbinding mode even when the default binding mode was not “move”, and they could specify mutability on such bindings withmut. In these editions, specifyingmuton a binding set the binding mode to “move” regardless of the current default binding mode.
마찬가지로, 참조 패턴은 기본 바인딩 모드가 “move“일 때만 나타날 수 있습니다. 예를 들어, 다음은 허용되지 않습니다:
#![allow(unused)]
fn main() {
let [&x] = &[&()]; //~ 오류
}
2024 Edition differences
Before the 2024 edition, reference patterns could appear even when the default binding mode was not “move”, and had both the effect of matching against the scrutinee and of causing the default binding mode to be reset to “move”.
이동 바인딩과 참조 바인딩은 동일한 패턴에서 혼합될 수 있습니다. 이렇게 하면 바인딩된 객체의 부분 이동(partial move)이 발생하며, 해당 객체는 이후에 사용할 수 없습니다. 이는 타입이 복사 가능하지 않은 경우에만 적용됩니다.
아래 예시에서 name 은 person 에서 이동됩니다. person 전체를 사용하거나 person.name 을 사용하려고 하면 부분 이동 으로 인해 오류가 발생합니다.
예:
#![allow(unused)]
fn main() {
struct Person {
name: String,
age: u8,
}
let person = Person{ name: String::from("John"), age: 23 };
// `name` 은 person에서 이동되고 `age` 는 참조됩니다
let Person { name, ref age } = person;
}
와일드카드 패턴
Syntax
WildcardPattern → _
와일드카드 패턴(밑줄 기호)은 모든 값과 매치됩니다. 이는 값이 중요하지 않을 때 값을 무시하는 데 사용됩니다.
다른 패턴 내부에서 이는 단일 데이터 필드와 매치됩니다(나머지 필드들과 매치되는 .. 와 대조적임).
식별자 패턴과 달리, 매치되는 값을 복사, 이동 또는 차용하지 않습니다.
예:
#![allow(unused)]
fn main() {
let x = 20;
let (a, _) = (10, x); // x는 항상 _와 매치됩니다
assert_eq!(a, 10);
// 함수/클로저 매개변수를 무시합니다
let real_part = |a: f64, _: f64| { a };
// 구조체의 필드를 무시합니다
struct RGBA {
r: f32,
g: f32,
b: f32,
a: f32,
}
let color = RGBA{r: 0.4, g: 0.1, b: 0.9, a: 0.5};
let RGBA{r: red, g: green, b: blue, a: _} = color;
assert_eq!(color.r, red);
assert_eq!(color.g, green);
assert_eq!(color.b, blue);
// 어떤 값이든 Some이면 허용합니다
let x = Some(10);
if let Some(_) = x {}
}
와일드카드 패턴은 항상 반박 불가능합니다.
Rest pattern
Syntax
RestPattern → ..
나머지 패턴(.. 토큰)은 가변 길이 패턴으로 작동하며, 앞뒤에서 이미 매치되지 않은 0개 이상의 요소와 매치됩니다.
이는 튜플, 튜플 구조체, 슬라이스 패턴에서만 사용될 수 있으며, 해당 패턴의 요소 중 하나로 단 한 번만 나타날 수 있습니다. 또한 슬라이스 패턴 에 한해서만 식별자 패턴 에서도 허용됩니다.
나머지 패턴은 항상 반박 불가능합니다.
예:
#![allow(unused)]
fn main() {
let words = vec!["a", "b", "c"];
let slice = &words[..];
match slice {
[] => println!("슬라이스가 비어 있음"),
[one] => println!("단일 요소 {}", one),
[head, tail @ ..] => println!("head={} tail={:?}", head, tail),
}
match slice {
// "!"여야 하는 마지막 요소를 제외한 모든 것을 무시합니다.
[.., "!"] => println!("!!!"),
// `start` 는 "z"여야 하는 마지막 요소를 제외한 모든 것의 슬라이스입니다.
[start @ .., "z"] => println!("다음으로 시작함: {:?}", start),
// `end` 는 "a"여야 하는 첫 번째 요소를 제외한 모든 것의 슬라이스입니다.
["a", end @ ..] => println!("다음으로 끝남: {:?}", end),
// 'whole'은 전체 슬라이스이고 `last` 는 마지막 요소입니다
whole @ [.., last] => println!("{:?}의 마지막 요소는 {}입니다", whole, last),
rest => println!("{:?}", rest),
}
if let [.., penultimate, _] = slice {
println!("마지막에서 두 번째는 {}입니다", penultimate);
}
let tuple = (1, 2, 3, 4, 5);
// The rest pattern may also be used in tuple and tuple
// struct patterns.
match tuple {
(1, .., y, z) => println!("y={} z={}", y, z),
(.., 5) => println!("꼬리(tail)는 5여야 함"),
(..) => println!("그 외 모든 것과 일치함"),
}
}
범위 패턴
Syntax
RangePattern →
RangeExclusivePattern
| RangeInclusivePattern
| RangeFromPattern
| RangeToExclusivePattern
| RangeToInclusivePattern
| ObsoleteRangePattern1
RangeExclusivePattern →
RangePatternBound .. RangePatternBound
RangeInclusivePattern →
RangePatternBound ..= RangePatternBound
RangeFromPattern →
RangePatternBound ..
RangeToExclusivePattern →
.. RangePatternBound
RangeToInclusivePattern →
..= RangePatternBound
ObsoleteRangePattern →
RangePatternBound … RangePatternBound
Range patterns match scalar values within the range defined by their bounds. They comprise a sigil (.. or ..=) and a bound on one or both sides.
A bound on the left of the sigil is called a lower bound. A bound on the right is called an upper bound.
The exclusive range pattern matches all values from the lower bound up to, but not including the upper bound. It is written as its lower bound, followed by .., followed by the upper bound.
For example, a pattern 'm'..'p' will match only 'm', 'n' and 'o', specifically not including 'p'.
The inclusive range pattern matches all values from the lower bound up to and including the upper bound. It is written as its lower bound, followed by ..=, followed by the upper bound.
For example, a pattern 'm'..='p' will match only the values 'm', 'n', 'o', and 'p'.
The from range pattern matches all values greater than or equal to the lower bound. It is written as its lower bound followed by ...
For example, 1.. will match any integer greater than or equal to 1, such as 1, 9, or 9001, or 9007199254740991 (if it is of an appropriate size), but not 0, and not negative numbers for signed integers.
The to exclusive range pattern matches all values less than the upper bound. It is written as .. followed by the upper bound.
For example, ..10 will match any integer less than 10, such as 9, 1, 0, and for signed integer types, all negative values.
The to inclusive range pattern matches all values less than or equal to the upper bound. It is written as ..= followed by the upper bound.
For example, ..=10 will match any integer less than or equal to 10, such as 10, 1, 0, and for signed integer types, all negative values.
A range pattern must be nonempty; it must span at least one value in the set of possible values for its type. In other words:
- In
a..=b, a ≤ b must be the case. For example, it is an error to have a range pattern10..=0, but10..=10is allowed. - In
a..b, a < b must be the case. For example, it is an error to have a range pattern10..0or10..10. - In
..b, b must not be the smallest value of its type. For example, it is an error to have a range pattern..-128i8or..f64::NEG_INFINITY.
A bound is written as one of:
- 문자, 바이트, 정수 또는 부동 소수점 리터럴.
- 정수 또는 부동 소수점 리터럴 앞에
-가 붙은 형태. - A path.
Note
We syntactically accept more than this for a RangePatternBound. We later reject the other things semantically.
If a bound is written as a path, after macro resolution, the path must resolve to a constant item of the type char, an integer type, or a float type.
The range pattern matches the type of its upper and lower bounds, which must be the same type.
If a bound is a path, the bound matches the type and has the value of the constant the path resolves to.
If a bound is a literal, the bound matches the type and has the value of the corresponding literal expression.
If a bound is a literal preceded by a -, the bound matches the same type as the corresponding literal expression and has the value of negating the value of the corresponding literal expression.
부동 소수점 범위 패턴의 경우, 상수는 NaN 일 수 없습니다.
예:
#![allow(unused)]
fn main() {
let c = 'f';
let valid_variable = match c {
'a'..='z' => true,
'A'..='Z' => true,
'α'..='ω' => true,
_ => false,
};
let ph = 10;
println!("{}", match ph {
0..7 => "산성",
7 => "중성",
8..=14 => "염기성",
_ => unreachable!(),
});
let uint: u32 = 5;
match uint {
0 => "0입니다!",
1.. => "양수입니다!",
};
// 상수에 대한 경로 사용:
const TROPOSPHERE_MIN : u8 = 6;
const TROPOSPHERE_MAX : u8 = 20;
const STRATOSPHERE_MIN : u8 = TROPOSPHERE_MAX + 1;
const STRATOSPHERE_MAX : u8 = 50;
const MESOSPHERE_MIN : u8 = STRATOSPHERE_MAX + 1;
const MESOSPHERE_MAX : u8 = 85;
let altitude = 70;
println!("{}", match altitude {
TROPOSPHERE_MIN..=TROPOSPHERE_MAX => "대류권",
STRATOSPHERE_MIN..=STRATOSPHERE_MAX => "성층권",
MESOSPHERE_MIN..=MESOSPHERE_MAX => "중간권",
_ => "아마도 외계 공간",
});
pub mod binary {
pub const MEGA : u64 = 1024*1024;
pub const GIGA : u64 = 1024*1024*1024;
}
let n_items = 20_832_425;
let bytes_per_item = 12;
if let size @ binary::MEGA..=binary::GIGA = n_items * bytes_per_item {
println!("크기가 적절하며 {}바이트를 차지함", size);
}
trait MaxValue {
const MAX: u64;
}
impl MaxValue for u8 {
const MAX: u64 = (1 << 8) - 1;
}
impl MaxValue for u16 {
const MAX: u64 = (1 << 16) - 1;
}
impl MaxValue for u32 {
const MAX: u64 = (1 << 32) - 1;
}
// 정규화된 경로 사용:
println!("{}", match 0xfacade {
0 ..= <u8 as MaxValue>::MAX => "u8에 맞음",
0 ..= <u16 as MaxValue>::MAX => "u16에 맞음",
0 ..= <u32 as MaxValue>::MAX => "u32에 맞음",
_ => "너무 큼",
});
}
고정 너비 정수 및 char 타입에 대한 범위 패턴이 타입의 가능한 모든 값 세트를 포괄하는 경우 반박 불가능(irrefutable)합니다. 예를 들어, 0u8..=255u8 은 반박 불가능합니다.
정수 타입의 값 범위는 최소값부터 최대값까지의 닫힌 범위입니다.
char 타입의 값 범위는 정확히 모든 유니코드 스칼라 값을 포함하는 범위인 '\u{0000}'..='\u{D7FF}' 와 '\u{E000}'..='\u{10FFFF}' 입니다.
RangeFromPattern cannot be used as a top-level pattern for subpatterns in slice patterns. For example, the pattern [1.., _] is not a valid pattern.
2021 Edition differences
Before the 2021 edition, range patterns with both a lower and upper bound may also be written using
...in place of..=, with the same meaning.
참조 패턴
Syntax
ReferencePattern → ( & | && ) mut? PatternWithoutRange
참조 패턴은 매치되는 포인터를 역참조하며, 따라서 이를 차용합니다.
예를 들어, x: &i32 에 대한 다음 두 매치는 동일합니다:
#![allow(unused)]
fn main() {
let int_reference = &3;
let a = match *int_reference { 0 => "0", _ => "some" };
let b = match int_reference { &0 => "0", _ => "some" };
assert_eq!(a, b);
}
참조 패턴에 대한 문법 생성물은 참조의 참조를 매치하기 위해 && 토큰과 매치되어야 합니다. 이는 그 자체로 하나의 토큰이며, 두 개의 & 토큰이 아니기 때문입니다.
mut 키워드를 추가하면 가변 참조를 역참조합니다. 가변성은 참조의 가변성과 일치해야 합니다.
참조 패턴은 항상 반박 불가능합니다.
구조체 패턴
Syntax
StructPattern →
PathInExpression {
StructPatternElements?
}
StructPatternElements →
StructPatternFields ( , | , StructPatternEtCetera )?
| StructPatternEtCetera
StructPatternFields →
StructPatternField ( , StructPatternField )*
StructPatternField →
OuterAttribute*
(
TUPLE_INDEX : Pattern
| IDENTIFIER : Pattern
| ref? mut? IDENTIFIER
)
구조체 패턴은 서브패턴에 의해 정의된 모든 기준을 만족하는 구조체, 열거형 및 공용체 값과 매치됩니다. 또한 구조체, 열거형 또는 공용체 값을 구조 분해 하는 데 사용됩니다.
구조체 패턴에서 필드는 이름이나 인덱스(튜플 구조체의 경우)로 참조되거나 .. 을 사용하여 무시됩니다.
#![allow(unused)]
fn main() {
struct Point {
x: u32,
y: u32,
}
let s = Point {x: 1, y: 1};
match s {
Point {x: 10, y: 20} => (),
Point {y: 10, x: 20} => (), // 순서는 중요하지 않음
Point {x: 10, ..} => (),
Point {..} => (),
}
struct PointTuple (
u32,
u32,
);
let t = PointTuple(1, 2);
match t {
PointTuple {0: 10, 1: 20} => (),
PointTuple {1: 10, 0: 20} => (), // 순서는 중요하지 않음
PointTuple {0: 10, ..} => (),
PointTuple {..} => (),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
}
let m = Message::Quit;
match m {
Message::Quit => (),
Message::Move {x: 10, y: 20} => (),
Message::Move {..} => (),
}
}
.. 이 사용되지 않으면, 구조체를 매치하는 데 사용되는 구조체 패턴은 모든 필드를 명시해야 합니다.
#![allow(unused)]
fn main() {
struct Struct {
a: i32,
b: char,
c: bool,
}
let mut struct_value = Struct{a: 10, b: 'X', c: false};
match struct_value {
Struct{a: 10, b: 'X', c: false} => (),
Struct{a: 10, b: 'X', ref c} => (),
Struct{a: 10, b: 'X', ref mut c} => (),
Struct{a: 10, b: 'X', c: _} => (),
Struct{a: _, b: _, c: _} => (),
}
}
공용체(union)를 매치하는 데 사용되는 구조체 패턴은 정확히 하나의 필드만 지정해야 합니다 (공용체에서의 패턴 매칭 참조)。
The IDENTIFIER syntax matches any value and binds it to a variable with the same name as the given field. It is a shorthand for fieldname: fieldname. The ref and mut qualifiers can be included with the behavior as described in patterns.ident.ref.
#![allow(unused)]
fn main() {
struct Struct {
a: i32,
b: char,
c: bool,
}
let struct_value = Struct{a: 10, b: 'X', c: false};
let Struct { a, b, c } = struct_value;
}
A struct pattern is refutable if the PathInExpression resolves to a constructor of an enum with more than one variant, or one of its subpatterns is refutable.
A struct pattern matches against the struct, union, or enum variant whose constructor is resolved from PathInExpression in the type namespace. See patterns.tuple-struct.namespace for more details.
튜플 구조체 패턴
Syntax
TupleStructPattern → PathInExpression ( TupleStructItems? )
TupleStructItems → Pattern ( , Pattern )* ,?
튜플 구조체 패턴은 서브패턴에 의해 정의된 모든 기준을 만족하는 튜플 구조체 및 열거형 값과 매치됩니다. 또한 튜플 구조체 또는 열거형 값을 구조 분해 하는 데 사용됩니다.
A tuple struct pattern is refutable if the PathInExpression resolves to a constructor of an enum with more than one variant, or one of its subpatterns is refutable.
A tuple struct pattern matches against the tuple struct or tuple-like enum variant whose constructor is resolved from PathInExpression in the value namespace.
Note
Conversely, a struct pattern for a tuple struct or tuple-like enum variant, e.g.
S { 0: _ }, matches against the tuple struct or variant whose constructor is resolved in the type namespace.enum E1 { V(u16) } enum E2 { V(u32) } // Import `E1::V` from the type namespace only. mod _0 { const V: () = (); // For namespace masking. pub(super) use super::E1::*; } use _0::*; // Import `E2::V` from the value namespace only. mod _1 { struct V {} // For namespace masking. pub(super) use super::E2::*; } use _1::*; fn f() { // This struct pattern matches against the tuple-like // enum variant whose constructor was found in the type // namespace. let V { 0: ..=u16::MAX } = (loop {}) else { loop {} }; // This tuple struct pattern matches against the tuple-like // enum variant whose constructor was found in the value // namespace. let V(..=u32::MAX) = (loop {}) else { loop {} }; } // Required due to the odd behavior of `super` within functions. fn main() {}The Lang team has made certain decisions, such as in PR #138458, that raise questions about the desirability of using the value namespace in this way for patterns, as described in PR #140593. It might be prudent to not intentionally rely on this nuance in your code.
튜플 패턴
Syntax
TuplePattern → ( TuplePatternItems? )
TuplePatternItems →
Pattern ,
| RestPattern
| Pattern ( , Pattern )+ ,?
튜플 패턴은 서브패턴에 의해 정의된 모든 기준을 만족하는 튜플 값과 매치됩니다. 또한 튜플을 구조 분해 하는 데 사용됩니다.
The form (..) with a single RestPattern is a special form that does not require a comma, and matches a tuple of any size.
튜플 패턴은 서브패턴 중 하나가 반박 가능한 경우 반박 가능합니다.
튜플 패턴 사용 예시:
#![allow(unused)]
fn main() {
let pair = (10, "ten");
let (a, b) = pair;
assert_eq!(a, 10);
assert_eq!(b, "ten");
}
그룹화된 패턴
Syntax
GroupedPattern → ( Pattern )
패턴을 괄호로 묶으면 복합 패턴의 우선순위를 명시적으로 제어할 수 있습니다. 예를 들어, &0..=5 와 같이 참조 패턴이 범위 패턴 옆에 오는 것은 모호하여 허용되지 않지만, 괄호를 사용하여 표현할 수 있습니다.
#![allow(unused)]
fn main() {
let int_reference = &3;
match int_reference {
&(0..=5) => (),
_ => (),
}
}
슬라이스 패턴
Syntax
SlicePattern → [ SlicePatternItems? ]
SlicePatternItems → Pattern ( , Pattern )* ,?
슬라이스 패턴은 고정 크기 배열과 동적 크기 슬라이스 모두와 매치될 수 있습니다.
#![allow(unused)]
fn main() {
// 고정 크기
let arr = [1, 2, 3];
match arr {
[1, _, _] => "1로 시작함",
[a, b, c] => "다른 것으로 시작함",
};
}
#![allow(unused)]
fn main() {
// 동적 크기
let v = vec![1, 2, 3];
match v[..] {
[a, b] => { /* 이 암(arm)은 길이가 일치하지 않으므로 적용되지 않습니다 */ }
[a, b, c] => { /* 이 암(arm)이 적용됩니다 */ }
_ => { /* 길이를 정적으로 알 수 없으므로 이 와일드카드가 필요합니다 */ }
};
}
슬라이스 패턴은 배열을 매치할 때 각 요소가 반박 불가능하다면 반박 불가능합니다.
When matching a slice, it is irrefutable only in the form with a single .. rest pattern or identifier pattern with the .. rest pattern as a subpattern.
슬라이스 내에서 하한과 상한이 모두 있지 않은 범위 패턴은 단일 슬라이스 요소와 매치하려는 의도임을 명확히 하기 위해 (a..) 와 같이 괄호로 묶어야 합니다. a..=b 와 같이 하한과 상한이 모두 있는 범위 패턴은 괄호로 묶을 필요가 없습니다.
경로 패턴
Syntax
PathPattern → PathExpression
경로 패턴 은 상수 값이나 필드가 없는 구조체 또는 열거형 변형을 참조하는 패턴입니다.
수식되지 않은(unqualified) 경로 패턴은 다음을 참조할 수 있습니다:
- 열거형 변형
- 구조체
- 상수
- 연관 상수
수식된(qualified) 경로 패턴은 연관 상수만 참조할 수 있습니다.
경로 패턴은 구조체를 참조하거나, 열거형이 단 하나의 변형만 가질 때 해당 열거형 변형을 참조하거나, 타입이 반박 불가능한 상수를 참조할 때 반박 불가능합니다. 여러 변형을 가진 열거형의 변형이나 반박 가능한 상수를 참조할 때는 반박 가능합니다.
상수 패턴
T 타입의 상수 C 가 패턴으로 사용될 때, 먼저 T: PartialEq 인지 확인합니다.
나아가 상수 C 의 값이 (재귀적) 구조적 동등성 을 가져야 하며, 이는 다음과 같이 재귀적으로 정의됩니다:
- 정수뿐만 아니라
str,bool,char값은 항상 구조적 동등성을 갖습니다.
- 튜플, 배열, 슬라이스는 모든 필드/요소가 구조적 동등성을 가지면 구조적 동등성을 갖습니다. (특히,
()와[]는 항상 구조적 동등성을 갖습니다.)
- 참조는 가리키는 값이 구조적 동등성을 가지면 구조적 동등성을 갖습니다.
struct또는enum타입의 값은PartialEq인스턴스가#[derive(PartialEq)]를 통해 파생되었고, 모든 필드(열거형의 경우 현재 활성화된 변형의 필드)가 구조적 동등성을 가지면 구조적 동등성을 갖습니다.
- 원시 포인터는 상수 정수로 정의된 경우(그 후 캐스팅되거나 transmute된 경우) 구조적 동등성을 갖습니다.
- 부동 소수점 값은
NaN이 아니면 구조적 동등성을 갖습니다.
- 그 외에는 구조적 동등성을 갖지 않습니다.
특히, 상수 C 의 값은 패턴 구축 시점(단형성화(monomorphization) 이전)에 알려져야 합니다. 이는 제네릭 파라미터가 포함된 연관 상수는 패턴으로 사용될 수 없음을 의미합니다.
The value of C must not contain any references to mutable statics (static mut items or interior mutable static items) or extern statics.
모든 조건이 충족되면, 상수 값은 패턴으로 번역되며 이제 해당 패턴을 직접 작성한 것과 정확히 동일하게 동작합니다. 특히, 이는 망라성 검사(exhaustiveness checking)에 완전히 참여합니다. (원시 포인터의 경우, 상수가 이러한 패턴을 작성하는 유일한 방법입니다. 이러한 타입들에 대해서는 오직 _ 만이 망라적인 것으로 간주됩니다.)
Or 패턴
Or-patterns are patterns that match on one of two or more sub-patterns (for example A | B | C). They can nest arbitrarily. Syntactically, or-patterns are allowed in any of the places where other patterns are allowed (represented by the Pattern production), with the exceptions of let-bindings and function and closure parameters (represented by the PatternNoTopAlt production).
정적 시맨틱
-
임의의 패턴
p와q에 대해 어떤 깊이에서든p | q패턴이 주어졌을 때, 다음의 경우 해당 패턴은 잘못 형성된(ill-formed) 것으로 간주됩니다:p에 대해 추론된 타입이q에 대해 추론된 타입과 통합(unify)되지 않거나,p와q에서 동일한 바인딩 집합이 도입되지 않거나,p와q에 있는 동일한 이름의 두 바인딩 타입이 타입 또는 바인딩 모드 관점에서 통합되지 않는 경우. 타입 통합은 모든 인스턴스에서 앞서 언급한 대로 정확해야 하며, 암시적인 타입 강제 변환 은 적용되지 않습니다.
match e_s { a_1 => e_1, ... a_n => e_n }표현식을 타입 검사할 때,p_i | q_i형식의 패턴을 포함하는 각 매치 암a_i에 대해, 해당 패턴이 존재하는 깊이d에서e_s의 깊이d조각의 타입이p_i | q_i와 통합(unify)되지 않으면p_i | q_i패턴은 잘못 형성된(ill-formed) 것으로 간주됩니다.
-
망라성 검사와 관련하여,
p | q패턴은p와q를 모두 포괄하는 것으로 간주됩니다. 어떤 생성자c(x, ..)에 대해 분배 법칙이 적용되어,c(p | q, ..rest)는c(p, ..rest) | c(q, ..rest)와 동일한 값 집합을 포괄합니다. 이는 최상위 레벨에 존재하는 패턴을 제외하고p | q형식의 중첩된 패턴이 더 이상 없을 때까지 재귀적으로 적용될 수 있습니다.여기서 “생성자” 는 튜플 구조체 패턴만을 가리키는 것이 아니라, 모든 곱 타입(product type)에 대한 패턴을 의미합니다. 여기에는 열거형 변형, 튜플 구조체, 이름 있는 필드가 있는 구조체, 배열, 튜플, 슬라이스가 포함됩니다.
동적 시맨틱
- 조사 대상 표현식
e_s를 깊이d에서c(p | q, ..rest)패턴(여기서c는 어떤 생성자,p와q는 임의의 패턴,rest는c의 나머지 선택적 요소)과 매치하는 동적 시맨틱은c(p, ..rest) | c(q, ..rest)와 동일한 것으로 정의됩니다.
구분 기호가 없는 다른 패턴과의 우선순위
이 장의 다른 곳에서 보여준 것처럼, 식별자 패턴, 참조 패턴, Or 패턴을 포함하여 구문상으로 구분 기호가 없는(undelimited) 여러 유형의 패턴이 있습니다. Or 패턴은 항상 가장 낮은 우선순위를 갖습니다. 이를 통해 향후 타입 어스크립션(type ascription) 기능을 위한 구문적 공간을 확보하고 모호성을 줄일 수 있습니다. 예를 들어, x @ A(..) | B(..) 는 x 가 모든 패턴에 바인딩되지 않았다는 오류를 발생시킵니다. &A(x) | B(x) 는 서로 다른 서브패턴에 있는 x 사이의 타입 불일치를 발생시킵니다.
-
The ObsoleteRangePattern syntax has been removed in the 2021 edition. ↩
타입 시스템
타입
러스트 프로그램의 모든 변수, 아이템, 값은 타입을 가집니다. 값 의 타입 은 해당 값을 보유한 메모리의 해석 방식과 해당 값에 대해 수행할 수 있는 연산을 정의합니다.
내장 타입(Built-in types)은 언어에 긴밀하게 통합되어 있으며, 사용자 정의 타입으로는 흉내 낼 수 없는 복잡한 방식으로 작동합니다.
사용자 정의 타입은 제한된 능력을 갖습니다.
타입 목록은 다음과 같습니다:
- 기본 타입(Primitive types):
- 시퀀스 타입:
- 사용자 정의 타입:
- 함수 타입:
- 포인터 타입:
- 트레잇 타입:
타입 표현식
Syntax
Type →
TypeNoBounds
| ImplTraitType
| TraitObjectType
TypeNoBounds →
ParenthesizedType
| ImplTraitTypeOneBound
| TraitObjectTypeOneBound
| TypePath
| TupleType
| NeverType
| RawPointerType
| ReferenceType
| ArrayType
| SliceType
| InferredType
| QualifiedPathInType
| BareFunctionType
| MacroInvocation
A type expression as defined in the Type grammar rule above is the syntax for referring to a type. It may refer to:
- 다음을 참조할 수 있는 타입 경로:
- 컴파일러에게 타입을 결정하도록 요청하는 추론된 타입.
- 모호성을 제거하기 위해 사용되는 괄호.
- 네버 타입.
- 타입 표현식으로 확장되는 매크로.
괄호로 둘러싸인 타입
Syntax
ParenthesizedType → ( Type )
In some situations the combination of types may be ambiguous. Use parentheses around a type to avoid ambiguity. For example, the + operator for type boundaries within a reference type is unclear where the boundary applies, so the use of parentheses is required. Grammar rules that require this disambiguation use the TypeNoBounds rule instead of Type.
#![allow(unused)]
fn main() {
use std::any::Any;
type T<'a> = &'a (dyn Any + Send);
}
재귀적 타입
명목적 타입(Nominal types)인 구조체, 열거형, 공용체 는 재귀적일 수 있습니다. 즉, 각 enum 변형이나 struct 또는 union 필드는 자신을 둘러싼 enum 또는 struct 타입 자체를 직접적 또는 간접적으로 참조할 수 있습니다.
이러한 재귀에는 제한 사항이 있습니다:
- 재귀적 타입은 재귀 과정에 반드시 명목적 타입을 포함해야 합니다(단순한 타입 별칭 이나 배열, 튜플 과 같은 다른 구조적 타입만으로는 안 됩니다). 따라서
type Rec = &'static [Rec]은 허용되지 않습니다. - 재귀적 타입의 크기는 유한해야 합니다. 다시 말해, 해당 타입의 재귀적 필드는 반드시 포인터 타입 이어야 합니다.
재귀적 타입과 그 사용 예시:
#![allow(unused)]
fn main() {
enum List<T> {
Nil,
Cons(T, Box<List<T>>)
}
let a: List<i32> = List::Cons(7, Box::new(List::Cons(13, Box::new(List::Nil))));
}
불리언 타입
#![allow(unused)]
fn main() {
let b: bool = true;
}
불리언 타입 또는 bool 은 true 와 false 라고 불리는 두 가지 값 중 하나를 가질 수 있는 기본 데이터 타입입니다.
이 타입의 값은 같은 이름의 값에 해당하는 true 및 false 키워드를 사용한 리터럴 표현식 을 통해 생성될 수 있습니다.
이 타입은 bool 이라는 이름으로 언어 프렐류드 의 일부입니다.
불리언 타입을 가진 객체는 크기와 정렬 이 각각 1입니다.
false 값은 비트 패턴 0x00 을 가지며, true 값은 비트 패턴 0x01 을 가집니다. 불리언 타입을 가진 객체가 그 외의 비트 패턴을 갖는 것은 정의되지 않은 동작(undefined behavior) 입니다.
불리언 타입은 다양한 표현식 에서 많은 피연산자의 타입으로 사용됩니다:
- 지연 불리언 연산자 표현식 의 피연산자
Note
The boolean type acts similarly to but is not an enumerated type. In practice, this mostly means that constructors are not associated to the type (e.g.
bool::true).
모든 기본 타입과 마찬가지로, 불리언 타입은 Clone, Copy, Sized, Send, Sync 트레잇 을 구현 합니다.
Note
See the standard library docs for library operations.
불리언 값에 대한 연산
When using certain operator expressions with a boolean type for its operands, they evaluate using the rules of boolean logic.
논리 부정 (Logical not)
b | !b |
|---|---|
true | false |
false | true |
논리 합 (Logical or)
a | b | a | b |
|---|---|---|
true | true | true |
true | false | true |
false | true | true |
false | false | false |
논리 곱 (Logical and)
a | b | a & b |
|---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
논리 배타적 합 (Logical xor)
a | b | a ^ b |
|---|---|---|
true | true | false |
true | false | true |
false | true | true |
false | false | false |
비교
a | b | a == b |
|---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | true |
a | b | a > b |
|---|---|---|
true | true | false |
true | false | true |
false | true | false |
false | false | false |
a != b는!(a == b)와 동일합니다
a >= b는a == b | a > b와 동일합니다
a < b는!(a >= b)와 동일합니다
a <= b는a == b | a < b와 동일합니다
비트 유효성
bool 의 단일 바이트는 초기화됨이 보장됩니다 (다시 말해, transmute::<bool, u8>(...) 는 항상 안전(sound)합니다. 하지만 일부 비트 패턴은 유효하지 않은 bool 값이므로, 그 역은 항상 안전하지는 않습니다).
숫자 타입
정수 타입
부호 없는 정수 타입은 다음과 같이 구성됩니다:
| 유형 | 최소값 | 최대값 |
|---|---|---|
u8 | 0 | 28-1 |
u16 | 0 | 216-1 |
u32 | 0 | 232-1 |
u64 | 0 | 264-1 |
u128 | 0 | 2128-1 |
부호 있는 2의 보수 정수 타입은 다음과 같이 구성됩니다:
| 유형 | 최소값 | 최대값 |
|---|---|---|
i8 | -(27) | 27-1 |
i16 | -(215) | 215-1 |
i32 | -(231) | 231-1 |
i64 | -(263) | 263-1 |
i128 | -(2127) | 2127-1 |
부동 소수점 타입
IEEE 754-2008 “binary32” 및 “binary64” 부동 소수점 타입은 각각 f32 와 f64 입니다.
머신 의존적 정수 타입
usize 타입은 플랫폼의 포인터 타입과 동일한 비트 수를 가진 부호 없는 정수 타입입니다. 이는 프로세스의 모든 메모리 주소를 나타낼 수 있습니다.
Note
While a
usizecan represent every address, converting a pointer to ausizeis not necessarily a reversible operation. For more information, see the documentation for type cast expressions,std::ptr, and provenance in particular.
The isize type is a signed two’s complement integer type with the same number of bits as the platform’s pointer type. The theoretical upper bound on object and array size is the maximum isize value. This ensures that isize can be used to calculate differences between pointers into an object or array and can address every byte within an object along with one byte past the end.
usize 및 isize 는 최소 16비트 너비입니다.
Note
Many pieces of Rust code may assume that pointers,
usize, andisizeare either 32-bit or 64-bit. As a consequence, 16-bit pointer support is limited and may require explicit care and acknowledgment from a library to support.
비트 유효성
모든 숫자 타입 T 에 대해, T 의 비트 유효성은 [u8; size_of::<T>()] 의 비트 유효성과 동일합니다. 초기화되지 않은 바이트는 유효한 u8 이 아닙니다.
Character type
The char type represents a single Unicode scalar value (i.e., a code point that is not a surrogate).
Example
#![allow(unused)] fn main() { let c: char = 'a'; let emoji: char = '😀'; let unicode: char = '\u{1F600}'; }
Note
See the standard library docs for information on the impls of the
chartype.
A value of type char is represented as a 32-bit unsigned word in the 0x0000 to 0xD7FF or 0xE000 to 0x10FFFF range. It is immediate undefined behavior to create a char that falls outside this range.
char 는 모든 플랫폼에서 u32 와 동일한 크기 및 정렬을 가짐이 보장됩니다.
Every byte of a char is guaranteed to be initialized. In other words, transmute::<char, [u8; size_of::<char>()]>(...) is always sound – but since some bit patterns are invalid chars, the inverse is not always sound.
String slice type
The string slice (str) type represents a sequence of characters.
#![allow(unused)]
fn main() {
let greeting1: &str = "Hello, world!";
let greeting2: &str = "你好,世界";
}
Note
See the standard library docs for information on the impls of the
strtype.
A value of type str is represented in the same way as [u8], a slice of 8-bit unsigned bytes.
Note
The standard library makes extra assumptions about
str: methods working onstrassume and ensure that the data it contains is valid UTF-8. Calling astrmethod with a non-UTF-8 buffer can cause undefined behavior now or in the future.
A str is a dynamically sized type. It can only be instantiated through a pointer type, such as &str. The layout of &str is the same as the layout of &[u8].
결코 리턴하지 않는 타입
Syntax
NeverType → !
네버 타입 ! 은 값이 없는 타입으로, 결코 완료되지 않는 계산의 결과를 나타냅니다.
! 타입의 표현식은 다른 어떤 타입으로도 강제 변환(coerced)될 수 있습니다.
현재 ! 타입은 함수의 반환 타입에만 나타날 수 있으며, 이는 함수가 결코 반환되지 않는 발산 함수(diverging function)임을 나타냅니다.
#![allow(unused)]
fn main() {
fn foo() -> ! {
panic!("이 호출은 결코 반환되지 않습니다.");
}
}
#![allow(unused)]
fn main() {
unsafe extern "C" {
pub safe fn no_return_extern_func() -> !;
}
}
튜플 타입
튜플 타입 은 다른 타입들의 이종 목록(heterogeneous list)을 위한 구조적 타입 1 군입니다.
튜플 타입의 구문은 괄호로 둘러싸인, 쉼표로 구분된 타입 목록입니다.
1-ary tuples require a comma after their element type to be disambiguated with a parenthesized type.
튜플 타입은 타입 목록의 길이와 동일한 수의 필드를 가집니다. 이 필드의 수는 튜플의 항수(arity) 를 결정합니다. n 개의 필드를 가진 튜플을 n-항 튜플(n-ary tuple) 이라고 합니다. 예를 들어, 2개의 필드를 가진 튜플은 2-항 튜플입니다.
튜플의 필드 이름은 타입 목록에서의 위치에 따라 증가하는 숫자를 사용하여 명명됩니다. 첫 번째 필드는 0, 두 번째 필드는 1 과 같은 식입니다. 각 필드의 타입은 튜플의 타입 목록에서 동일한 위치에 있는 타입입니다.
편의상 그리고 역사적인 이유로, 필드가 없는 튜플 타입(())은 흔히 유닛(unit) 또는 유닛 타입 이라고 불립니다. 이 타입의 유일한 값 또한 유닛 또는 유닛 값 이라고 불립니다.
튜플 타입의 몇 가지 예시:
()(유닛)(i32,)(1-항 튜플)(f64, f64)(String, i32)(i32, String)(이전 예시와는 다른 타입임)(i32, f64, Vec<String>, Option<bool>)
이 타입의 값은 튜플 표현식 을 사용하여 생성됩니다. 나아가, 다양한 표현식들이 평가할 다른 의미 있는 값이 없을 때 유닛 값을 생성합니다.
튜플 필드는 튜플 인덱스 표현식 이나 패턴 매칭 을 통해 접근할 수 있습니다.
배열 타입
Syntax
ArrayType → [ Type ; Expression ]
배열은 T 타입의 요소 N 개로 구성된 고정 크기 시퀀스입니다. 배열 타입은 [T; N] 으로 작성됩니다.
예:
#![allow(unused)]
fn main() {
// 스택에 할당된 배열
let array: [i32; 3] = [1, 2, 3];
// 힙에 할당된 배열, 슬라이스로 강제 변환됨
let boxed_array: Box<[i32]> = Box::new([1, 2, 3]);
}
배열의 모든 요소는 항상 초기화되며, 안전한 메서드 및 연산자에서의 배열 접근은 항상 경계 검사(bounds-checked)가 이루어집니다.
Note
The
Vec<T>standard library type provides a heap-allocated resizable array type.
슬라이스 타입
슬라이스는 T 타입 요소 시퀀스에 대한 ’뷰(view)’를 나타내는 동적 크기 타입(dynamically sized type) 입니다. 슬라이스 타입은 [T] 로 작성됩니다.
슬라이스 타입은 일반적으로 포인터 타입을 통해 사용됩니다. 예시:
&[T]: ’공유 슬라이스’로, 흔히 그냥 ’슬라이스’라고 불립니다. 가리키는 데이터를 소유하지 않고 차용합니다.&mut [T]: ’가변 슬라이스’입니다. 가리키는 데이터를 가변적으로 차용합니다.Box<[T]>: ‘박스드 슬라이스(boxed slice)’
예:
#![allow(unused)]
fn main() {
// 힙에 할당된 배열, 슬라이스로 강제 변환됨
let boxed_array: Box<[i32]> = Box::new([1, 2, 3]);
// 배열에 대한 (공유) 슬라이스
let slice: &[i32] = &boxed_array[..];
}
슬라이스의 모든 요소는 항상 초기화되며, 안전한 메서드 및 연산자에서의 슬라이스 접근은 항상 경계 검사(bounds-checked)가 이루어집니다.
구조체 타입
struct 타입 은 해당 타입의 필드 라고 불리는 다른 타입들의 이종 곱(heterogeneous product)입니다.1
struct 의 새 인스턴스는 구조체 표현식 을 통해 생성될 수 있습니다.
struct 의 메모리 레이아웃은 필드 재정렬과 같은 컴파일러 최적화를 허용하기 위해 기본적으로 정의되지 않지만, repr 속성 을 사용하여 고정할 수 있습니다. 어떤 경우든 대응하는 구조체 표현식 에서는 필드를 어떤 순서로든 제공할 수 있으며, 결과물인 struct 값은 항상 동일한 메모리 레이아웃을 갖습니다.
struct 의 필드는 모듈 외부에서 구조체 데이터에 접근할 수 있도록 가시성 수정자(visibility modifiers) 로 수식될 수 있습니다.
튜플 구조체 타입은 필드가 익명이라는 점을 제외하면 구조체 타입과 동일합니다.
유닛 형태 구조체 타입은 필드가 없다는 점을 제외하면 구조체 타입과 같습니다. 연관된 구조체 표현식 에 의해 생성된 유일한 값이 이 타입에 속하는 유일한 값입니다.
-
struct타입은 C의struct타입, ML 언어군의 레코드(record) 타입, 또는 Lisp 언어군의 struct 타입과 유사합니다. ↩
열거 타입
열거형 타입(enumerated type) 은 명목적이고 이종의 서로소 합집합(disjoint union) 타입이며, enum 아이템 의 이름으로 나타내어집니다. 1
enum 아이템 은 타입과 여러 개의 변형(variants) 을 선언하며, 각 변형은 독립적인 이름을 가지고 구조체, 튜플 구조체 또는 유닛 형태 구조체의 구문을 가집니다.
enum 의 새 인스턴스는 구조체 표현식 을 통해 생성될 수 있습니다.
모든 enum 값은 대응하는 enum 타입의 가장 큰 변형만큼의 메모리와 판별자(discriminant)를 저장하는 데 필요한 크기를 소비합니다.
열거형 타입은 타입으로서 구조적으로 나타낼 수 없으며, 반드시 enum 아이템 에 대한 이름 있는 참조로 나타내어야 합니다.
-
enum타입은 Haskell의data생성자 선언이나 Limbo의 pick ADT 와 유사합니다. ↩
공용체 타입
공용체 타입(union type) 은 명목적이고 이종의 C와 유사한 공용체이며, union 아이템 의 이름으로 나타내어집니다.
공용체에는 “활성 필드“라는 개념이 없습니다. 대신, 모든 공용체 접근은 공용체 내용의 일부를 접근된 필드의 타입으로 transmute합니다.
transmute는 예기치 않거나 정의되지 않은 동작을 유발할 수 있으므로, 공용체 필드에서 읽으려면 unsafe 가 필요합니다.
공용체 필드 타입은 또한 드롭(drop)이 전혀 필요하지 않음을 보장하는 타입의 서브셋으로 제한됩니다. 자세한 내용은 아이템 문서를 참조하십시오.
union 의 메모리 레이아웃은 기본적으로 정의되지 않으며(특히, 필드가 오프셋 0에 위치할 필요가 없음), #[repr(...)] 속성을 사용하여 레이아웃을 고정할 수 있습니다.
함수 아이템 타입
참조될 때, 함수 아이템이나 튜플 형태 구조체 또는 열거형 변형의 생성자는 해당 함수 아이템 타입 의 크기가 0인 값을 생성합니다.
해당 타입은 함수(이름, 타입 인자, 조기 바인딩된(early-bound) 라이프타임 인자 등. 단, 함수 호출 시에만 할당되는 지연 바인딩된(late-bound) 라이프타임 인자는 제외)를 명시적으로 식별합니다. 따라서 값에 실제 함수 포인터를 포함할 필요가 없으며, 함수 호출 시 간접 참조(indirection)가 필요하지 않습니다.
함수 아이템 타입을 직접 참조하는 구문은 없지만, 컴파일러는 오류 메시지에서 해당 타입을 fn(u32) -> i32 {fn_name} 과 같이 표시합니다.
함수 아이템 타입은 함수를 명시적으로 식별하기 때문에, 서로 다른 함수(서로 다른 아이템, 또는 제네릭이 다른 동일 아이템)의 아이템 타입은 구별되며, 이들을 혼합하면 타입 오류가 발생합니다:
#![allow(unused)]
fn main() {
fn foo<T>() { }
let x = &mut foo::<i32>;
*x = foo::<u32>; //~ ERROR 타입 불일치
}
그러나 함수 아이템에서 동일한 시그니처를 가진 함수 포인터 로의 강제 변환(coercion) 이 존재합니다. 이는 함수 포인터가 직접 요구되는 위치에 함수 아이템이 사용될 때뿐만 아니라, 동일한 시그니처를 가진 서로 다른 함수 아이템 타입들이 동일한 if 또는 match 의 서로 다른 암(arm)에서 만날 때도 발생합니다.
#![allow(unused)]
fn main() {
let want_i32 = false;
fn foo<T>() { }
// 여기서 `foo_ptr_1` 은 함수 포인터 타입 `fn()` 을 가집니다
let foo_ptr_1: fn() = foo::<i32>;
// ... `foo_ptr_2` 도 마찬가지입니다 - 이 코드는 타입 검사를 통과합니다.
let foo_ptr_2 = if want_i32 {
foo::<i32>
} else {
foo::<u32>
};
}
All function items implement Copy, Clone, Send, and Sync.
Fn, FnMut, and FnOnce are implemented unless the function has any of the following:
- an
unsafequalifier - a
target_featureattribute - an ABI other than
"Rust"
클로저 타입
클로저 표현식 은 직접 작성할 수 없는 고유하고 익명인 타입을 가진 클로저 값을 생성합니다. 클로저 타입은 캡처된 값들을 포함하는 구조체와 거의 동일합니다. 예를 들어, 다음과 같은 클로저는:
#![allow(unused)]
fn main() {
#[derive(Debug)]
struct Point { x: i32, y: i32 }
struct Rectangle { left_top: Point, right_bottom: Point }
fn f<F : FnOnce() -> String> (g: F) {
println!("{}", g());
}
let mut rect = Rectangle {
left_top: Point { x: 1, y: 1 },
right_bottom: Point { x: 0, y: 0 }
};
let c = || {
rect.left_top.x += 1;
rect.right_bottom.x += 1;
format!("{:?}", rect.left_top)
};
f(c); // "Point { x: 2, y: 1 }"을 출력합니다.
}
대략 다음과 같은 클로저 타입을 생성합니다:
// 참고: 이것은 실제 번역되는 방식과 정확히 일치하지 않으며, 단지 설명을 위한 것입니다.
struct Closure<'a> {
left_top : &'a mut Point,
right_bottom_x : &'a mut i32,
}
impl<'a> FnOnce<()> for Closure<'a> {
type Output = String;
extern "rust-call" fn call_once(self, args: ()) -> String {
self.left_top.x += 1;
*self.right_bottom_x += 1;
format!("{:?}", self.left_top)
}
}
그리하여 f 에 대한 호출이 다음과 같이 작동하도록 합니다:
f(Closure{ left_top: &mut rect.left_top, right_bottom_x: &mut rect.right_bottom.x });
캡처 모드
캡처 모드 는 환경의 장소 표현식(place expression) 이 클로저 내부로 어떻게 차용되거나 이동되는지를 결정합니다. 캡처 모드는 다음과 같습니다:
- 불변 차용 (
ImmBorrow) — 장소 표현식이 공유 참조 로 캡처됩니다. - 고유 불변 차용 (
UniqueImmBorrow) — 이는 불변 차용과 유사하지만, 아래 에 설명된 대로 고유해야 합니다. - 가변 차용 (
MutBorrow) — 장소 표현식이 가변 참조 로 캡처됩니다. - 이동 (
ByValue) — 값을 이동 시켜서 장소 표현식을 캡처합니다.
환경의 장소 표현식은 클로저 본문 내에서 캡처된 값이 사용되는 방식과 호환되는 첫 번째 모드로 캡처됩니다. 캡처 모드는 관련된 변수나 필드의 라이프타임, 또는 클로저 자체의 라이프타임과 같은 클로저 주변의 코드에 의해 영향을 받지 않습니다.
Copy 값
클로저 내부로 이동된 Copy 를 구현하는 값은 ImmBorrow 모드로 캡처됩니다.
#![allow(unused)]
fn main() {
let x = [0; 1024];
let c = || {
let y = x; // x는 ImmBorrow로 캡처됨
};
}
비동기 입력 캡처
비동기 클로저는 본문에서 사용되는지 여부에 관계없이 항상 모든 입력 인자를 캡처합니다.
Capture precision
A capture path is a sequence starting with a variable from the environment followed by zero or more place projections from that variable.
A place projection is a field access, tuple index, dereference (and automatic dereferences), array or slice index expression, or pattern destructuring applied to a variable.
Note
In
rustc, pattern destructuring desugars into a series of dereferences and field or element accesses.
클로저는 캡처 경로를 차용하거나 이동하며, 이는 아래에 설명된 규칙에 따라 잘릴(truncated) 수 있습니다.
예:
#![allow(unused)]
fn main() {
struct SomeStruct {
f1: (i32, i32),
}
let s = SomeStruct { f1: (1, 2) };
let c = || {
let x = s.f1.1; // s.f1.1은 ImmBorrow로 캡처됨
};
c();
}
여기서 캡처 경로는 로컬 변수 s, 필드 접근 .f1, 그리고 튜플 인덱스 .1 로 이어집니다. 이 클로저는 s.f1.1 의 불변 차용을 캡처합니다.
공유 접두어
In the case where a capture path and one of the ancestors of that path are both captured by a closure, the ancestor path is captured with the highest capture mode among the two captures, CaptureMode = max(AncestorCaptureMode, DescendantCaptureMode), using the strict weak ordering:
ImmBorrow < UniqueImmBorrow < MutBorrow < ByValue
이는 재귀적으로 적용되어야 할 수도 있음에 유의하십시오.
#![allow(unused)]
fn main() {
// 이 예시에서, 공유된 조상을 가진 세 가지 서로 다른 캡처 경로가 있습니다:
fn move_value<T>(_: T){}
let s = String::from("S");
let t = (s, String::from("T"));
let mut u = (t, String::from("U"));
let c = || {
println!("{:?}", u); // u는 ImmBorrow로 캡처됨
u.1.truncate(0); // u.0은 MutBorrow로 캡처됨
move_value(u.0.0); // u.0.0은 ByValue로 캡처됨
};
c();
}
전체적으로 이 클로저는 u 를 ByValue 로 캡처하게 됩니다.
가장 오른쪽 공유 참조 자르기
공유 참조에 대해 역참조가 적용되는 경우, 캡처 경로는 가장 오른쪽 역참조 지점에서 잘립니다.
이러한 자르기가 허용되는 이유는 공유 참조를 통해 읽는 필드는 항상 공유 참조나 복사를 통해 읽히기 때문입니다. 이는 추가적인 정밀도가 차용 검사 관점에서 아무런 이득을 주지 않을 때 캡처 크기를 줄이는 데 도움이 됩니다.
가장 오른쪽 역참조인 이유는 필요 이상으로 짧은 라이프타임을 피하기 위해서입니다. 다음 예시를 보십시오:
#![allow(unused)]
fn main() {
struct Int(i32);
struct B<'a>(&'a i32);
struct MyStruct<'a> {
a: &'static Int,
b: B<'a>,
}
fn foo<'a, 'b>(m: &'a MyStruct<'b>) -> impl FnMut() + 'static {
let c = || drop(&m.a.0);
c
}
}
만약 이것이 m 을 캡처한다면, m 은 'a 로 제한되어 있으므로 클로저는 더 이상 'static 보다 오래 살 수 없게 됩니다. 대신, (*(*m).a) 를 ImmBorrow 로 캡처합니다.
와일드카드 패턴 바인딩
Closures only capture data that needs to be read. Binding a value with a wildcard pattern does not read the value, so the place is not captured.
#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
let x = S;
let c = || {
let _ = x; // Does not capture `x`.
};
let c = || match x {
_ => (), // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();
}
Destructuring tuples, structs, and single-variant enums does not, by itself, cause a read or the place to be captured.
Note
Enums marked with
#[non_exhaustive]from other crates are always treated as having multiple variants. See type.closure.capture.precision.discriminants.non_exhaustive.
#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
// Destructuring tuples does not cause a read or capture.
let x = (S,);
let c = || {
let (..) = x; // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();
// Destructuring unit structs does not cause a read or capture.
let x = S;
let c = || {
let S = x; // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();
// Destructuring structs does not cause a read or capture.
struct W<T>(T);
let x = W(S);
let c = || {
let W(..) = x; // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();
// Destructuring single-variant enums does not cause a read
// or capture.
enum E<T> { V(T) }
let x = E::V(S);
let c = || {
let E::V(..) = x; // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();
}
Fields matched against RestPattern (..) or StructPatternEtCetera (also ..) are not read, and those fields are not captured.
#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
let x = (S, S);
let c = || {
let (x0, ..) = x; // Captures `x.0` by `ByValue`.
};
// Only the first tuple field was captured by the closure.
x.1; // OK: `x.1` can be moved here.
c();
}
Partial captures of arrays and slices are not supported; the entire slice or array is always captured even if used with wildcard pattern matching, indexing, or sub-slicing.
#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
let mut x = [S, S];
let c = || {
let [x0, _] = x; // Captures all of `x` by `ByValue`.
};
let _ = &mut x[1]; // ERROR: Borrow of moved value.
}
와일드카드와 매치되는 값들도 여전히 초기화되어 있어야 합니다.
#![allow(unused)]
fn main() {
let x: u8;
let c = || {
let _ = x; // ERROR: Binding `x` isn't initialized.
};
}
Capturing for discriminant reads
If pattern matching reads a discriminant, the place containing that discriminant is captured by ImmBorrow.
Matching against a variant of an enum that has more than one variant reads the discriminant, capturing the place by ImmBorrow.
#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
let mut x = (Some(S), S);
let c = || match x {
(None, _) => (),
// ^^^^
// This pattern requires reading the discriminant, which
// causes `x.0` to be captured by `ImmBorrow`.
_ => (),
};
let _ = &mut x.0; // ERROR: Cannot borrow `x.0` as mutable.
// ^^^
// The closure is still live, so `x.0` is still immutably
// borrowed here.
c();
}
#![allow(unused)]
fn main() {
struct S; // A non-`Copy` type.
let x = (Some(S), S);
let c = || match x { // Captures `x.0` by `ImmBorrow`.
(None, _) => (),
_ => (),
};
// Though `x.0` is captured due to the discriminant read,
// `x.1` is not captured.
x.1; // OK: `x.1` can be moved here.
c();
}
Matching against the only variant of a single-variant enum does not read the discriminant and does not capture the place.
#![allow(unused)]
fn main() {
enum E<T> { V(T) } // A single-variant enum.
let x = E::V(());
let c = || {
let E::V(_) = x; // Does not capture `x`.
};
x; // OK: `x` can be moved here.
c();
}
If #[non_exhaustive] is applied to an enum, the enum is treated as having multiple variants for the purpose of deciding whether a read occurs, even if it actually has only one variant.
Even if all variants but the one being matched against are uninhabited, making the pattern irrefutable, the discriminant is still read if it otherwise would be.
#![allow(unused)]
fn main() {
enum Empty {}
let mut x = Ok::<_, Empty>(42);
let c = || {
let Ok(_) = x; // Captures `x` by `ImmBorrow`.
};
let _ = &mut x; // ERROR: Cannot borrow `x` as mutable.
c();
}
Capturing and range patterns
Matching against a range pattern reads the place being matched, even if the range includes all possible values of the type, and captures the place by ImmBorrow.
#![allow(unused)]
fn main() {
let mut x = 0u8;
let c = || {
let 0..=u8::MAX = x; // Captures `x` by `ImmBorrow`.
};
let _ = &mut x; // ERROR: Cannot borrow `x` as mutable.
c();
}
Capturing and slice patterns
Matching a slice against a slice pattern other than one with only a single rest pattern (i.e. [..]) is treated as a read of the length from the slice and captures the slice by ImmBorrow.
#![allow(unused)]
fn main() {
let x: &mut [u8] = &mut [];
let c = || match x { // Captures `*x` by `ImmBorrow`.
&mut [] => (),
// ^^
// This matches a slice of exactly zero elements. To know whether the
// scrutinee matches, the length must be read, causing the slice to
// be captured.
_ => (),
};
let _ = &mut *x; // ERROR: Cannot borrow `*x` as mutable.
c();
}
#![allow(unused)]
fn main() {
let x: &mut [u8] = &mut [];
let c = || match x { // Does not capture `*x`.
[..] => (),
// ^^ Rest pattern.
};
let _ = &mut *x; // OK: `*x` can be borrow here.
c();
}
Note
Perhaps surprisingly, even though the length is contained in the (wide) pointer to the slice, it is the place of the pointee (the slice) that is treated as read and is captured.
#![allow(unused)] fn main() { fn f<'l: 's, 's>(x: &'s mut &'l [u8]) -> impl Fn() + 'l { // The closure outlives `'l` because it captures `**x`. If // instead it captured `*x`, it would not live long enough // to satisfy the `impl Fn() + 'l` bound. || match *x { // Captures `**x` by `ImmBorrow`. &[] => (), _ => (), } } }In this way, the behavior is consistent with dereferencing to the slice in the scrutinee.
#![allow(unused)] fn main() { fn f<'l: 's, 's>(x: &'s mut &'l [u8]) -> impl Fn() + 'l { || match **x { // Captures `**x` by `ImmBorrow`. [] => (), _ => (), } } }For details, see Rust PR #138961.
As the length of an array is fixed by its type, matching an array against a slice pattern does not by itself capture the place.
#![allow(unused)]
fn main() {
let x: [u8; 1] = [0];
let c = || match x { // Does not capture `x`.
[_] => (), // Length is fixed.
};
x; // OK: `x` can be moved here.
c();
}
이동 컨텍스트에서의 참조 캡처
참조에서 필드를 끄집어내어 이동하는 것은 허용되지 않으므로, move 클로저는 참조의 첫 번째 역참조 전까지의 캡처 경로 접두어만 캡처합니다. 참조 자체는 클로저 내부로 이동됩니다.
#![allow(unused)]
fn main() {
struct T(String, String);
let mut t = T(String::from("foo"), String::from("bar"));
let t_mut_ref = &mut t;
let mut c = move || {
t_mut_ref.0.push_str("123"); // `t_mut_ref` 를 ByValue로 캡처함
};
c();
}
원시 포인터 역참조
원시 포인터를 역참조하는 것은 unsafe 하므로, 클로저는 원시 포인터의 첫 번째 역참조 전까지의 캡처 경로 접두어만 캡처합니다.
#![allow(unused)]
fn main() {
struct T(String, String);
let t = T(String::from("foo"), String::from("bar"));
let t_ptr = &t as *const T;
let c = || unsafe {
println!("{}", (*t_ptr).0); // `t_ptr` 을 ImmBorrow로 캡처함
};
c();
}
공용체 필드
공용체 필드에 접근하는 것은 unsafe 하므로, 클로저는 공용체 자체까지만의 캡처 경로 접두어를 캡처합니다.
#![allow(unused)]
fn main() {
union U {
a: (i32, i32),
b: bool,
}
let u = U { a: (123, 456) };
let c = || {
let x = unsafe { u.a.0 }; // `u` 를 ByValue로 캡처함
};
c();
// 이는 필드에 쓰는 경우도 포함합니다.
let mut u = U { a: (123, 456) };
let mut c = || {
u.b = true; // `u` 를 MutBorrow로 캡처함
};
c();
}
정렬되지 않은 struct 로의 참조
Because it is undefined behavior to create references to unaligned fields in a structure, closures will only capture the prefix of the capture path that runs up to, but not including, the first field access into a structure that uses the packed representation. This includes all fields, even those that are aligned, to protect against compatibility concerns should any of the fields in the structure change in the future.
#![allow(unused)]
fn main() {
#[repr(packed)]
struct T(i32, i32);
let t = T(2, 5);
let c = || {
let a = t.0; // `t` 를 ImmBorrow로 캡처함
};
// `t` 에서 복사하는 것은 괜찮습니다.
let (a, b) = (t.0, t.1);
c();
}
마찬가지로, 정렬되지 않은 필드의 주소를 취하는 것도 구조체 전체를 캡처합니다:
#![allow(unused)]
fn main() {
#[repr(packed)]
struct T(String, String);
let mut t = T(String::new(), String::new());
let c = || {
let a = std::ptr::addr_of!(t.1); // `t` 를 ImmBorrow로 캡처함
};
let a = t.0; // ERROR: `t.0` 이 차용되었으므로 이동할 수 없음
c();
}
하지만 packed가 아니라면 필드를 정밀하게 캡처하므로 위 코드가 작동합니다:
#![allow(unused)]
fn main() {
struct T(String, String);
let mut t = T(String::new(), String::new());
let c = || {
let a = std::ptr::addr_of!(t.1); // `t.1` 을 ImmBorrow로 캡처함
};
// 여기서의 이동은 허용됩니다.
let a = t.0;
c();
}
Box 대 다른 Deref 구현체
Box 에 대한 Deref 트레잇 구현은 특별한 엔티티로 간주되어 다른 Deref 구현과 다르게 취급됩니다.
예를 들어, Rc 와 Box 가 포함된 예시를 보겠습니다. *rc 는 Rc 에 정의된 트레잇 메서드 deref 의 호출로 디슈거링(desugar)되지만, *box 는 다르게 취급되므로 Box 내부 콘텐츠의 정밀한 캡처가 가능합니다.
non-move 클로저에서의 Box
non-move 클로저에서 Box 의 내용물이 클로저 본문으로 이동되지 않는다면, Box 의 내용물은 정밀하게 캡처됩니다.
#![allow(unused)]
fn main() {
struct S(String);
let b = Box::new(S(String::new()));
let c_box = || {
let x = &(*b).0; // `(*b).0` 을 ImmBorrow로 캡처함
};
c_box();
// `Box` 를 Deref를 구현하는 다른 타입과 비교해 보십시오:
let r = std::rc::Rc::new(S(String::new()));
let c_rc = || {
let x = &(*r).0; // `r` 을 ImmBorrow로 캡처함
};
c_rc();
}
그러나 Box 의 내용물이 클로저로 이동된다면, 박스 전체가 캡처됩니다. 이는 클로저로 이동해야 하는 데이터의 양을 최소화하기 위함입니다.
#![allow(unused)]
fn main() {
// 클로저가 참조를 취하는 대신 값을 이동시킨다는 점을 제외하면 위 예시와 동일합니다.
struct S(String);
let b = Box::new(S(String::new()));
let c_box = || {
let x = (*b).0; // `b` 를 ByValue로 캡처함
};
c_box();
}
move 클로저에서의 Box
non-move 클로저에서 Box 의 내용을 이동시키는 것과 유사하게, move 클로저에서 Box 의 내용을 읽는 것은 Box 전체를 캡처하게 됩니다.
#![allow(unused)]
fn main() {
struct S(i32);
let b = Box::new(S(10));
let c_box = move || {
let x = (*b).0; // `b` 를 ByValue로 캡처함
};
}
캡처에서의 고유 불변 차용
캡처는 고유 불변 차용(unique immutable borrow) 이라고 불리는 특수한 종류의 차용을 통해 발생할 수 있습니다. 이는 언어의 다른 어디에서도 사용될 수 없으며 명시적으로 작성할 수도 없습니다. 다음 예시와 같이 가변 참조의 대상(referent)을 수정할 때 발생합니다:
#![allow(unused)]
fn main() {
let mut b = false;
let x = &mut b;
let mut c = || {
// `x` 의 ImmBorrow 및 MutBorrow.
let a = &x;
*x = true; // `x` 는 UniqueImmBorrow로 캡처됨
};
// 다음 줄은 오류입니다:
// let y = &x;
c();
// 그러나 다음은 괜찮습니다.
let z = &x;
}
In this case, borrowing x mutably is not possible, because x is not mut. But at the same time, borrowing x immutably would make the assignment illegal, because a & &mut reference might not be unique, so it cannot safely be used to modify a value. So a unique immutable borrow is used: it borrows x immutably, but like a mutable borrow, it must be unique.
위의 예시에서 y 의 선언을 주석 해제하면 클로저의 x 차용에 대한 고유성을 위반하므로 오류가 발생합니다. z의 선언은 블록 끝에서 클로저의 라이프타임이 만료되어 차용이 해제되었으므로 유효합니다.
호출 트레잇 및 강제 변환
모든 클로저 타입은 FnOnce 를 구현하며, 이는 클로저의 소유권을 소비함으로써 한 번 호출될 수 있음을 나타냅니다. 또한 일부 클로저는 더 구체적인 호출 트레잇을 구현합니다:
- 캡처된 변수 중 어느 것도 밖으로 이동시키지 않는 클로저는
FnMut를 구현하며, 이는 가변 참조로 호출될 수 있음을 나타냅니다.
- 캡처된 변수를 변경하거나 밖으로 이동시키지 않는 클로저는
Fn를 구현하며, 이는 공유 참조로 호출될 수 있음을 나타냅니다.
Note
moveclosures may still implementFnorFnMut, even though they capture variables by move. This is because the traits implemented by a closure type are determined by what the closure does with captured values, not how it captures them.
비캡처 클로저(Non-capturing closures) 는 환경에서 아무것도 캡처하지 않는 클로저입니다. 비동기가 아닌 비캡처 클로저는 일치하는 시그니처를 가진 함수 포인터(예: fn())로 강제 변환될 수 있습니다.
#![allow(unused)]
fn main() {
let add = |x, y| x + y;
let mut x = add(5,7);
type Binop = fn(i32, i32) -> i32;
let bo: Binop = add;
x = bo(5,7);
}
비동기 클로저 트레잇
비동기 클로저는 FnMut 또는 Fn 구현 여부에 대해 추가적인 제한을 가집니다.
비동기 클로저가 반환하는 Future 는 클로저와 유사한 캡처 특성을 가집니다. 이는 사용 방식에 따라 비동기 클로저로부터 장소 표현식을 캡처합니다. 비동기 클로저가 다음 속성 중 하나를 가지면 Future 에게 대여(lending) 중이라고 합니다:
Future가 가변 캡처를 포함하는 경우.- 비동기 클로저가 값으로 캡처하는 경우 (단, 역참조 투영을 통해 값에 접근하는 경우는 제외).
비동기 클로저가 Future 에게 대여 중인 경우, FnMut 및 Fn 은 구현되지 않습니다. FnOnce 는 항상 구현됩니다.
예시: 가변 캡처에 대한 첫 번째 조건은 다음과 같이 설명될 수 있습니다:
#![allow(unused)] fn main() { fn takes_callback<Fut: Future>(c: impl FnMut() -> Fut) {} fn f() { let mut x = 1i32; let c = async || { x = 2; // x는 MutBorrow로 캡처됨 }; takes_callback(c); // ERROR: 비동기 클로저가 `FnMut` 를 구현하지 않음 } }일반 값 캡처에 대한 두 번째 조건은 다음과 같이 설명될 수 있습니다:
#![allow(unused)] fn main() { fn takes_callback<Fut: Future>(c: impl Fn() -> Fut) {} fn f() { let x = &1i32; let c = async move || { let a = x + 2; // x는 ByValue로 캡처됨 }; takes_callback(c); // ERROR: 비동기 클로저가 `Fn` 을 구현하지 않음 } }두 번째 조건의 예외는 역참조를 사용하여 설명될 수 있으며, 이 경우
Fn및FnMut구현이 허용됩니다:#![allow(unused)] fn main() { fn takes_callback<Fut: Future>(c: impl Fn() -> Fut) {} fn f() { let x = &1i32; let c = async move || { let a = *x + 2; }; takes_callback(c); // OK: `Fn` 을 구현함 } }
비동기 클로저는 일반 클로저가 Fn, FnMut, FnOnce 을 구현하는 것과 유사한 방식으로 AsyncFn, AsyncFnMut, AsyncFnOnce 을 구현합니다. 즉, 본문에서 캡처된 변수들이 어떻게 사용되느냐에 따라 결정됩니다.
기타 트레잇
모든 클로저 타입은 Sized 를 구현합니다. 또한 클로저 타입은 저장된 캡처 타입들이 허용하는 경우 다음 트레잇들을 구현합니다:
Send 및 Sync 에 대한 규칙은 일반 구조체 타입과 일치하며, Clone 및 Copy 는 마치 파생(derived) 된 것처럼 작동합니다. Clone 의 경우, 캡처된 값들이 클로닝되는 순서는 지정되지 않습니다.
캡처는 종종 참조에 의해 발생하므로 다음과 같은 일반적인 규칙이 나타납니다:
- 모든 캡처된 값이
Sync이면 클로저는Sync입니다. - 비고유 불변 참조로 캡처된 모든 값이
Sync이고, 고유 불변 참조나 가변 참조, 복사 또는 이동으로 캡처된 모든 값이Send이면 클로저는Send입니다. - 클로저가 고유 불변 참조나 가변 참조로 값을 캡처하지 않고, 복사나 이동으로 캡처하는 모든 값이 각각
Clone또는Copy이면 클로저는Clone또는Copy입니다.
Drop order
클로저가 구조체, 튜플, 열거형과 같은 복합 타입의 필드를 값으로 캡처하면, 해당 필드의 라이프타임은 이제 클로저에 묶이게 됩니다. 결과적으로 복합 타입의 서로 다른 필드들이 서로 다른 시점에 드롭될 수 있습니다.
#![allow(unused)]
fn main() {
{
let tuple =
(String::from("foo"), String::from("bar")); // --+
{ // |
let c = || { // ----------------------------+ |
// tuple.0은 클로저 내부로 캡처됨 | |
drop(tuple.0); // | |
}; // | |
} // 'c'와 'tuple.0'이 여기서 드롭됨 ------------+ |
} // tuple.1이 여기서 드롭됨 -----------------------------+
}
2018 에디션 및 이전
클로저 타입의 차이점
2018 에디션 및 이전 버전에서, 클로저는 항상 변수 전체를 캡처하며 정밀한 캡처 경로를 사용하지 않습니다. 이는 클로저 타입 섹션에서 사용된 예시의 경우, 생성된 클로저 타입이 대신 다음과 같은 모습이 됨을 의미합니다:
struct Closure<'a> {
rect : &'a mut Rectangle,
}
impl<'a> FnOnce<()> for Closure<'a> {
type Output = String;
extern "rust-call" fn call_once(self, args: ()) -> String {
self.rect.left_top.x += 1;
self.rect.right_bottom.x += 1;
format!("{:?}", self.rect.left_top)
}
}
그리고 f 에 대한 호출은 다음과 같이 작동합니다:
f(Closure { rect: rect });
캡처 정밀도의 차이
구조체, 튜플, 열거형과 같은 복합 타입은 개별 필드가 아니라 항상 전체가 캡처됩니다. 결과적으로, 단일 필드만 캡처하려면 로컬 변수로 차용해야 할 수도 있습니다:
#![allow(unused)]
fn main() {
use std::collections::HashSet;
struct SetVec {
set: HashSet<u32>,
vec: Vec<u32>
}
impl SetVec {
fn populate(&mut self) {
let vec = &mut self.vec;
self.set.iter().for_each(|&n| {
vec.push(n);
})
}
}
}
만약 클로저가 self.vec 을 직접 사용했다면 self 를 가변 참조로 캡처하려고 시도했을 것입니다. 하지만 self.set 이 이미 반복을 위해 차용된 상태이므로 코드가 컴파일되지 않았을 것입니다.
move 키워드가 사용되면, 차용이 가능하더라도 모든 캡처는 이동(또는 Copy 타입의 경우 복사)에 의해 이루어집니다. move 키워드는 보통 클로저가 반환되거나 새 스레드를 생성하는 데 사용되는 경우와 같이, 클로저가 캡처된 값보다 더 오래 살아남을 수 있도록 하기 위해 사용됩니다.
와일드카드 패턴의 경우처럼 클로저가 데이터를 실제로 읽는지 여부와 관계없이, 클로저 외부에서 정의된 변수가 클로저 내부에서 언급되면 해당 변수는 전체가 캡처됩니다.
드롭 순서의 차이
복합 타입은 전체가 캡처되므로, 이러한 복합 타입 중 하나를 값으로 캡처하는 클로저는 클로저가 드롭될 때 캡처된 변수 전체를 동시에 드롭합니다.
#![allow(unused)]
fn main() {
{
let tuple =
(String::from("foo"), String::from("bar"));
{
let c = || { // --------------------------+
// tuple은 클로저 내부로 캡처됨 |
// --------------------------+
// tuple은 클로저 내부로 캡처됨 |
drop(tuple.0); // |
}; // |
} // 'c'와 'tuple'이 여기서 드롭됨 ------------+
}
}
포인터 타입
모든 포인터는 명시적인 일급 객체(first-class values)입니다. 이들은 이동하거나 복사할 수 있고, 데이터 구조체에 저장할 수 있으며, 함수에서 반환될 수도 있습니다.
참조 (& 및 &mut)
Syntax
ReferenceType → & Lifetime? mut? TypeNoBounds
공유 참조 (&)
공유 참조는 다른 어떤 값에 의해 소유된 메모리를 가리킵니다.
값에 대한 공유 참조가 생성되면, 해당 값의 직접적인 변경이 방지됩니다. 내부 가변성 은 특정 상황에서 이에 대한 예외를 제공합니다. 이름에서 알 수 있듯이, 값에 대한 공유 참조는 몇 개든지 존재할 수 있습니다. 공유 참조 타입은 &type 으로 작성되거나, 명시적인 라이프타임을 지정해야 할 경우 &'a type 으로 작성됩니다.
참조 복사는 “얕은(shallow)” 연산입니다. 이는 포인터 자체만 복사하며, 즉 포인터는 Copy 입니다. 참조를 해제하는 것은 가리키는 값에 아무런 영향을 주지 않지만, 임시 값 을 참조하는 경우 참조 자체의 스코프 동안 그 값을 유지합니다.
가변 참조 (&mut)
가변 참조는 다른 어떤 값에 의해 소유된 메모리를 가리킵니다. 가변 참조 타입은 &mut type 또는 &'a mut type 으로 작성됩니다.
(차용되지 않은) 가변 참조는 가리키는 값에 접근할 수 있는 유일한 방법이므로, Copy 가 아닙니다.
원시 포인터 (*const 및 *mut)
Syntax
RawPointerType → * ( mut | const ) TypeNoBounds
원시 포인터는 안전성이나 생존성(liveness) 보장이 없는 포인터입니다. 원시 포인터는 *const T 또는 *mut T 로 작성됩니다. 예를 들어 *const i32 는 32비트 정수에 대한 원시 포인터를 의미합니다.
원시 포인터를 복사하거나 드롭하는 것은 다른 어떤 값의 수명 주기에도 영향을 미치지 않습니다.
원시 포인터를 역참조하는 것은 unsafe 연산 입니다.
이는 원시 포인터를 다시 차용(&* 또는 &mut *)하여 참조로 변환하는 데에도 사용될 수 있습니다. 원시 포인터는 일반적으로 권장되지 않습니다. 이들은 외부 코드와의 상호 운용성을 지원하고, 성능에 민감하거나 저수준 함수를 작성하기 위해 존재합니다.
원시 포인터를 비교할 때는 가리키는 대상이 아니라 주소로 비교합니다. 동적 크기 타입 에 대한 원시 포인터를 비교할 때는 추가 데이터도 함께 비교됩니다.
원시 포인터는 *const 포인터의 경우 &raw const 를, *mut 포인터의 경우 &raw mut 을 사용하여 직접 생성할 수 있습니다.
Smart pointers
표준 라이브러리는 참조와 원시 포인터 외에도 추가적인 ‘스마트 포인터’ 타입을 포함합니다.
비트 유효성
대부분의 플랫폼에서 방출되는 기계어 코드상으로 포인터와 참조가 usize 와 유사함에도 불구하고, 참조나 포인터 타입을 비포인터 타입으로 transmute하는 것의 의미론은 현재 미정입니다. 따라서 포인터나 참조 타입 P 를 [u8; size_of::<P>()] 로 transmute하는 것은 유효하지 않을 수 있습니다.
얇은(thin) 원시 포인터의 경우(즉, T: Sized 에 대해 P = *const T 또는 P = *mut T), 역방향(정수 또는 정수 배열에서 P 로 transmute)은 항상 유효합니다. 그러나 이러한 transmute를 통해 생성된 포인터는 역참조할 수 없습니다(T 의 크기가 0이어도 마찬가지입니다).
함수 포인터 타입
Syntax
BareFunctionType →
ForLifetimes? FunctionTypeQualifiers fn
( FunctionParametersMaybeNamedVariadic? ) BareFunctionReturnType?
FunctionTypeQualifiers → unsafe? ( extern Abi? )?
BareFunctionReturnType → -> TypeNoBounds
FunctionParametersMaybeNamedVariadic →
MaybeNamedFunctionParameters | MaybeNamedFunctionParametersVariadic
MaybeNamedFunctionParameters →
MaybeNamedParam ( , MaybeNamedParam )* ,?
MaybeNamedParam →
OuterAttribute* ( ( IDENTIFIER | _ ) : )? Type
MaybeNamedFunctionParametersVariadic →
( MaybeNamedParam , )* MaybeNamedParam , OuterAttribute* …
A function pointer type, written using the fn keyword, refers to a function whose identity is not necessarily known at compile-time.
Binop 이 함수 포인터 타입으로 정의된 예시:
#![allow(unused)]
fn main() {
fn add(x: i32, y: i32) -> i32 {
x + y
}
let mut x = add(5,7);
type Binop = fn(i32, i32) -> i32;
let bo: Binop = add;
x = bo(5,7);
}
함수 포인터는 함수 아이템 및 비캡처, 비동기가 아닌 클로저 로부터의 강제 변환을 통해 생성될 수 있습니다.
unsafe 한정자는 해당 타입의 값이 unsafe 함수 임을 나타내며, extern 한정자는 이것이 extern 함수 임을 나타냅니다.
For the function to be variadic, its extern ABI must be one of those listed in items.extern.variadic.conventions.
함수 포인터 매개변수의 속성
함수 포인터 매개변수의 속성은 일반 함수 매개변수 와 동일한 규칙 및 제한을 따릅니다.
트레잇 객체
Syntax
TraitObjectType → dyn? TypeParamBounds
트레잇 객체 는 트레잇 집합을 구현하는 다른 타입의 불투명한(opaque) 값입니다. 트레잇 집합은 dyn 호환(dyn compatible) 기반 트레잇 과 임의 개수의 자동 트레잇 으로 구성됩니다.
트레잇 객체는 기반 트레잇, 해당 자동 트레잇, 그리고 기반 트레잇의 모든 상위 트레잇(supertraits) 을 구현합니다.
트레잇 객체는 dyn 키워드 뒤에 트레잇 바운드 집합이 오는 형태로 작성되지만, 트레잇 바운드에 다음과 같은 제한이 있습니다.
비자동 트레잇은 하나를 초과할 수 없고, 라이프타임도 하나를 초과할 수 없으며, 제외(opt-out) 바운드(예: ?Sized)는 허용되지 않습니다. 또한, 트레잇 경로는 괄호로 묶을 수 있습니다.
예를 들어, Trait 라는 트레잇이 주어졌을 때, 다음은 모두 트레잇 객체입니다:
dyn Traitdyn Trait + Senddyn Trait + Send + Syncdyn Trait + 'staticdyn Trait + Send + 'staticdyn Trait +dyn 'static + Trait.dyn (Trait)
2021 Edition differences
Before the 2021 edition, the
dynkeyword may be omitted.
2018 Edition differences
In the 2015 edition, if the first bound of the trait object is a path that starts with
::, then thedynwill be treated as a part of the path. The first path can be put in parenthesis to get around this. As such, if you want a trait object with the trait::your_module::Trait, you should write it asdyn (::your_module::Trait).2018 에디션부터
dyn은 진정한 키워드이며 경로에 허용되지 않으므로, 괄호가 필요하지 않습니다.
기반 트레잇이 서로 별칭(alias) 관계이고 자동 트레잇 집합과 라이프타임 바운드가 동일하다면, 두 트레잇 객체 타입은 서로 별칭 관계입니다. 예를 들어, dyn Trait + Send + UnwindSafe 는 dyn Trait + UnwindSafe + Send 와 같습니다.
값이 어떤 구체적인 타입인지 불투명하기 때문에, 트레잇 객체는 동적 크기 타입 입니다. 모든 DST와 마찬가지로, 트레잇 객체는 &dyn SomeTrait 또는 Box<dyn SomeTrait> 와 같이 어떤 포인터 타입 뒤에서 사용됩니다. 트레잇 객체에 대한 포인터의 각 인스턴스는 다음을 포함합니다:
SomeTrait를 구현하는 타입T의 인스턴스에 대한 포인터- 가상 메서드 테이블(흔히 vtable 이라고 함)은
T가 구현하는SomeTrait및 그 상위 트레잇 의 각 메서드에 대해T의 구현에 대한 포인터(즉, 함수 포인터)를 포함합니다.
트레잇 객체의 목적은 메서드의 “지연 바인딩(late binding)“을 허용하는 것입니다. 트레잇 객체에서 메서드를 호출하면 런타임에 가상 디스패치(virtual dispatch)가 발생합니다. 즉, 트레잇 객체 vtable에서 함수 포인터를 로드하여 간접적으로 호출합니다. 각 vtable 항목에 대한 실제 구현은 객체마다 다를 수 있습니다.
트레잇 객체의 예시:
trait Printable {
fn stringify(&self) -> String;
}
impl Printable for i32 {
fn stringify(&self) -> String { self.to_string() }
}
fn print(a: Box<dyn Printable>) {
println!("{}", a.stringify());
}
fn main() {
print(Box::new(10) as Box<dyn Printable>);
}
이 예시에서, 트레잇 Printable 은 print 의 타입 시그니처와 main 의 캐스트 표현식 양쪽 모두에서 트레잇 객체로 나타납니다.
Trait object lifetime bounds
트레잇 객체는 참조를 포함할 수 있으므로, 해당 참조들의 라이프타임은 트레잇 객체의 일부로 표현되어야 합니다. 이 라이프타임은 Trait + 'a 로 작성됩니다. 기본값 이 있어 보통은 이 라이프타임이 합리적인 선택으로 추론될 수 있게 합니다.
Impl 트레잇
Syntax
ImplTraitType → impl TypeParamBounds
ImplTraitTypeOneBound → impl TraitBound
impl Trait 는 특정 트레잇을 구현하는 이름은 없지만 구체적인 타입을 지정하는 방법을 제공합니다. 이는 두 가지 위치에 나타날 수 있습니다: 인자 위치(함수에 대한 익명 타입 매개변수 역할)와 반환 위치(추상 반환 타입 역할)입니다.
#![allow(unused)]
fn main() {
trait Trait {}
impl Trait for () {}
// 인자 위치: 익명 타입 매개변수
fn foo(arg: impl Trait) {
}
// 반환 위치: 추상 반환 타입
fn bar() -> impl Trait {
}
}
익명 타입 매개변수
Note
This is often called “impl Trait in argument position”. (The term “parameter” is more correct here, but “impl Trait in argument position” is the phrasing used during the development of this feature, and it remains in parts of the implementation.)
함수는 impl 뒤에 트레잇 바운드 집합을 사용하여 매개변수가 익명 타입을 갖도록 선언할 수 있습니다. 호출자는 익명 타입 매개변수에 선언된 바운드를 만족하는 타입을 제공해야 하며, 함수는 익명 타입 매개변수의 트레잇 바운드를 통해 사용할 수 있는 메서드만 사용할 수 있습니다.
예를 들어, 다음 두 형식은 거의 동일합니다:
#![allow(unused)]
fn main() {
trait Trait {}
// 제네릭 타입 매개변수
fn with_generic_type<T: Trait>(arg: T) {
}
// 인자 위치의 impl Trait
fn with_impl_trait(arg: impl Trait) {
}
}
That is, impl Trait in argument position is syntactic sugar for a generic type parameter like <T: Trait>, except that the type is anonymous and doesn’t appear in the GenericParams list.
Note
For function parameters, generic type parameters and
impl Traitare not exactly equivalent. With a generic parameter such as<T: Trait>, the caller has the option to explicitly specify the generic argument forTat the call site using GenericArgs, for example,foo::<usize>(1). Changing a parameter from either one to the other can constitute a breaking change for the callers of a function, since this changes the number of generic arguments.
추상 반환 타입
Note
This is often called “impl Trait in return position”.
함수는 impl Trait 를 사용하여 추상 반환 타입을 반환할 수 있습니다. 이러한 타입은 호출자가 지정된 Trait 에 의해 선언된 메서드만 사용할 수 있는 다른 구체적인 타입을 대신합니다.
함수에서 가능한 각 반환 값은 동일한 구체적인 타입으로 해석되어야 합니다.
반환 위치의 impl Trait 는 함수가 박싱되지 않은(unboxed) 추상 타입을 반환할 수 있게 합니다. 이는 클로저 및 반복자와 함께 사용할 때 특히 유용합니다. 예를 들어, 클로저는 작성할 수 없는 고유한 타입을 가집니다. 이전에는 함수에서 클로저를 반환하는 유일한 방법이 트레잇 객체 를 사용하는 것이었습니다:
#![allow(unused)]
fn main() {
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
}
이는 힙 할당 및 동적 디스패치로 인한 성능 저하를 초래할 수 있습니다. 클로저의 타입을 완전히 명시하는 것은 불가능했고, 오직 Fn 트레잇만 사용할 수 있었습니다. 즉, 트레잇 객체가 필요하다는 뜻입니다. 하지만 impl Trait 를 사용하면 이를 더 간단하게 작성할 수 있습니다:
#![allow(unused)]
fn main() {
fn returns_closure() -> impl Fn(i32) -> i32 {
|x| x + 1
}
}
이는 또한 박싱된 트레잇 객체 사용의 단점을 피할 수 있게 해줍니다.
마찬가지로, 반복자의 구체적인 타입은 체인에 있는 이전의 모든 반복자의 타입을 포함하여 매우 복잡해질 수 있습니다. impl Iterator 를 반환한다는 것은 함수가 관련된 다른 모든 반복자 타입을 명시적으로 지정하는 대신, 반환 타입에 대한 바운드로서 Iterator 트레잇만을 노출한다는 것을 의미합니다.
트레잇 및 트레잇 구현에서의 반환 위치 impl Trait
트레잇 내의 함수들도 익명 연관 타입을 위한 구문으로 impl Trait 를 사용할 수 있습니다.
트레잇 내 연관 함수의 반환 타입에 있는 모든 impl Trait 는 익명 연관 타입으로 디슈거링(desugared)됩니다. 구현의 함수 시그니처에 나타나는 반환 타입이 연관 타입의 값을 결정하는 데 사용됩니다.
캡처링
각 반환 위치 impl Trait 추상 타입 뒤에는 숨겨진 구체적인 타입이 있습니다. 이 구체적인 타입이 제네릭 매개변수를 사용하려면, 해당 제네릭 매개변수가 추상 타입에 의해 캡처 되어야 합니다.
자동 캡처링
반환 위치의 impl Trait 추상 타입은 제네릭 타입, 상수, 라이프타임 매개변수(고차원(higher-ranked) 매개변수 포함)를 포함한 스코프 내의 모든 제네릭 매개변수를 자동으로 캡처합니다.
2024 Edition differences
Before the 2024 edition, on free functions and on associated functions and methods of inherent impls, generic lifetime parameters that do not appear in the bounds of the abstract return type are not automatically captured.
정밀 캡처링
반환 위치의 impl Trait 추상 타입에 의해 캡처되는 제네릭 매개변수 집합은 use<..> 바운드 를 사용하여 명시적으로 제어할 수 있습니다. 이것이 존재하면, use<..> 바운드에 나열된 제네릭 매개변수만 캡처됩니다. 예:
#![allow(unused)]
fn main() {
fn capture<'a, 'b, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> {
// ~~~~~~~~~~~~~~~~~~~~~~~
// 오직 `'a` 와 `T` 만 캡처합니다.
(x, y)
}
}
Currently, only one use<..> bound may be present in a bounds list, all in-scope type and const generic parameters must be included, and all lifetime parameters that appear in other bounds of the abstract type must be included.
use<..> 바운드 내에서, 존재하는 모든 라이프타임 매개변수는 모든 타입 및 상수 제네릭 매개변수보다 앞에 나타나야 하며, impl Trait 반환 타입 내에서 허용되는 경우 생략된 라이프타임('_)이 나타날 수 있습니다.
스코프 내의 모든 타입 매개변수가 이름으로 포함되어야 하므로, 인자 위치 impl Trait 를 사용하는 아이템의 시그니처에는 use<..> 바운드를 사용할 수 없습니다. 해당 아이템들은 스코프 내에 익명 타입 매개변수를 갖기 때문입니다.
Any use<..> bound that is present in an associated function in a trait definition must include all generic parameters of the trait, including the implicit Self generic type parameter of the trait.
반환 위치에서의 제네릭과 impl Trait 의 차이점
인자 위치에서 impl Trait 는 의미론적으로 제네릭 타입 매개변수와 매우 유사합니다. 그러나 반환 위치에서는 둘 사이에 중요한 차이가 있습니다. impl Trait 를 사용하면 제네릭 타입 매개변수와 달리 함수가 반환 타입을 선택하며, 호출자는 반환 타입을 선택할 수 없습니다.
다음 함수는:
#![allow(unused)]
fn main() {
trait Trait {}
fn foo<T: Trait>() -> T {
// ...
panic!()
}
}
호출자가 반환 타입 T 를 결정하도록 허용하며, 함수는 해당 타입을 반환합니다.
다음 함수는:
#![allow(unused)]
fn main() {
trait Trait {}
impl Trait for () {}
fn foo() -> impl Trait {
// ...
}
}
호출자가 반환 타입을 결정하는 것을 허용하지 않습니다. 대신 함수가 반환 타입을 선택하지만, 오직 그 타입이 Trait 를 구현한다는 것만 약속합니다.
제한 사항
impl Trait 는 extern 이 아닌 함수의 매개변수 또는 반환 타입으로만 나타날 수 있습니다. let 바인딩의 타입이나 필드 타입이 될 수 없으며, 타입 별칭 내부에 나타날 수 없습니다.
타입 파라미터
타입 매개변수 선언이 있는 아이템의 본문 내에서, 해당 타입 매개변수의 이름은 타입입니다:
#![allow(unused)]
fn main() {
fn to_vec<A: Clone>(xs: &[A]) -> Vec<A> {
if xs.is_empty() {
return vec![];
}
let first: A = xs[0].clone();
let mut rest: Vec<A> = to_vec(&xs[1..]);
rest.insert(0, first);
rest
}
}
여기서 first 는 to_vec 의 A 타입 매개변수를 참조하는 A 타입을 가지며, rest 는 요소 타입이 A 인 벡터 Vec<A> 타입을 가집니다.
추론된 타입
Syntax
InferredType → _
추론된 타입은 컴파일러에게 사용 가능한 주변 정보를 기반으로 가능한 경우 타입을 추론하도록 요청합니다.
Example
The inferred type is often used in generic arguments:
#![allow(unused)] fn main() { let x: Vec<_> = (0..10).collect(); }
The inferred type cannot be used in item signatures.
Dynamically sized types
Most types have a fixed size that is known at compile time and implement the trait Sized. A type with a size that is known only at run-time is called a dynamically sized type (DST) or, informally, an unsized type. Slices, trait objects, and str are examples of DSTs.
이러한 타입은 특정 경우에만 사용될 수 있습니다:
- DST에 대한 포인터 타입 은 크기가 있지만, 크기가 있는 타입에 대한 포인터보다 두 배의 크기를 가집니다.
- Pointers to slices and
stralso store the number of elements. - 트레잇 객체에 대한 포인터는 vtable에 대한 포인터도 저장합니다.
- Pointers to slices and
- DST는 특별한
?Sized바운드를 가진 제네릭 타입 매개변수에 타입 인자로 제공될 수 있습니다. 또한 대응하는 연관 타입 선언에?Sized바운드가 있을 때 연관 타입 정의에도 사용될 수 있습니다. 기본적으로 모든 타입 매개변수나 연관 타입은?Sized를 사용하여 완화되지 않는 한Sized바운드를 가집니다.
- 트레잇은 DST에 대해 구현될 수 있습니다. 제네릭 타입 매개변수와 달리, 트레잇 정의에서는
Self: ?Sized가 기본값입니다.
- 구조체는 마지막 필드로 DST를 포함할 수 있으며, 이 경우 구조체 자체도 DST가 됩니다.
타입 레이아웃
타입의 레이아웃은 크기, 정렬, 그리고 필드들의 상대적 오프셋을 의미합니다. 열거형의 경우, 판별자(discriminant)가 어떻게 배치되고 해석되는지 또한 타입 레이아웃의 일부입니다.
타입 레이아웃은 컴파일할 때마다 변경될 수 있습니다. 정확히 어떻게 수행되는지 문서화하려고 시도하는 대신, 우리는 현재 보장되는 내용만을 문서화합니다.
동일한 레이아웃을 가진 타입이라도 함수 경계를 넘어 전달되는 방식은 다를 수 있다는 점에 유의하십시오. 타입의 함수 호출 ABI 호환성에 대해서는 여기 를 참조하십시오.
Size and alignment
모든 값은 정렬과 크기를 가집니다.
값의 정렬(alignment) 은 해당 값을 저장하기에 유효한 주소가 무엇인지 지정합니다. 정렬이 n 인 값은 반드시 n의 배수인 주소에만 저장되어야 합니다. 예를 들어, 정렬이 2인 값은 짝수 주소에 저장되어야 하며, 정렬이 1인 값은 아무 주소에나 저장될 수 있습니다. 정렬은 바이트 단위로 측정되며, 최소 1이어야 하고 항상 2의 거듭제곱이어야 합니다. 값의 정렬은 align_of_val 함수로 확인할 수 있습니다.
값의 크기(size) 는 해당 아이템 타입을 가진 배열에서 연속된 요소 사이의 바이트 단위 오프셋이며, 정렬 패딩을 포함합니다. 값의 크기는 항상 정렬의 배수입니다. 일부 타입은 크기가 0일 수 있음에 유의하십시오; 0은 모든 정렬의 배수로 간주됩니다(예를 들어, 일부 플랫폼에서 [u16; 0] 타입은 크기가 0이고 정렬이 2입니다). 값의 크기는 size_of_val 함수로 확인할 수 있습니다.
모든 값이 동일한 크기와 정렬을 가지며 컴파일 타임에 둘 다 알려진 타입은 Sized 트레잇을 구현하고, size_of 및 align_of 함수로 확인할 수 있습니다. Sized 가 아닌 타입은 동적 크기 타입 으로 알려져 있습니다. Sized 타입의 모든 값은 동일한 크기와 정렬을 공유하므로, 이러한 공유된 값을 각각 해당 타입의 크기와 타입의 정렬이라고 부릅니다.
Primitive data layout
대부분의 기본 타입들의 크기는 이 표에 주어져 있습니다.
| 유형 | size_of::<Type>() |
|---|---|
bool | 1 |
u8 / i8 | 1 |
u16 / i16 | 2 |
u32 / i32 | 4 |
u64 / i64 | 8 |
u128 / i128 | 16 |
usize / isize | 아래 참조 |
f32 | 4 |
f64 | 8 |
char | 4 |
usize 와 isize 는 대상 플랫폼의 모든 주소를 포함할 수 있을 만큼 큰 크기를 가집니다. 예를 들어, 32비트 대상에서는 4바이트이고, 64비트 대상에서는 8바이트입니다.
기본 타입의 정렬은 플랫폼에 따라 다릅니다. 대부분의 경우 정렬은 크기와 같지만, 더 작을 수도 있습니다. 특히 i128 과 u128 은 크기가 16임에도 불구하고 종종 4 또는 8 바이트로 정렬되며, 많은 32비트 플랫폼에서 i64, u64, f64 는 8이 아닌 4 바이트로만 정렬됩니다.
Pointers and references layout
포인터와 참조는 동일한 레이아웃을 가집니다. 포인터나 참조의 가변성은 레이아웃을 변경하지 않습니다.
크기가 있는 타입에 대한 포인터는 usize 와 동일한 크기와 정렬을 가집니다.
크기가 없는 타입(unsized types)에 대한 포인터는 크기를 가집니다. 크기와 정렬은 적어도 포인터의 크기 및 정렬과 동일함이 보장됩니다.
Note
Though you should not rely on this, all pointers to DSTs are currently twice the size of the size of
usizeand have the same alignment.
Array layout
[T; N] 배열은 size_of::<T>() * N 의 크기를 가지며 T 와 동일한 정렬을 가집니다. 배열은 0부터 시작하는 nth 요소가 배열 시작점으로부터 n * size_of::<T>() 바이트만큼 오프셋되도록 배치됩니다.
Slice layout
슬라이스는 자신이 슬라이싱하는 배열 섹션과 동일한 레이아웃을 가집니다.
Note
This is about the raw
[T]type, not pointers (&[T],Box<[T]>, etc.) to slices.
str 레이아웃
String slices are a UTF-8 representation of characters that have the same layout as slices of type [u8]. A reference &str has the same layout as a reference &[u8].
Tuple layout
튜플은 Rust 표현 에 따라 배치됩니다.
이에 대한 예외는 유닛 튜플(())로, 크기가 0이고 정렬이 1인 0크기 타입(zero-sized type)으로 보장됩니다.
Trait object layout
트레잇 객체는 해당 트레잇 객체가 나타내는 값과 동일한 레이아웃을 가집니다.
Note
This is about the raw trait object types, not pointers (
&dyn Trait,Box<dyn Trait>, etc.) to trait objects.
Closure layout
클로저는 레이아웃 보장이 없습니다.
표현 (Representations)
모든 사용자 정의 복합 타입(struct, enum, union)은 해당 타입의 레이아웃이 무엇인지 지정하는 표현(representation) 을 가집니다.
타입에 대해 가능한 표현은 다음과 같습니다:
타입의 표현은 repr 속성을 적용하여 변경할 수 있습니다. 다음 예시는 C 표현을 가진 구조체를 보여줍니다.
#![allow(unused)]
fn main() {
#[repr(C)]
struct ThreeInts {
first: i16,
second: i8,
third: i32
}
}
정렬은 각각 align 과 packed 수정자를 사용하여 높이거나 낮출 수 있습니다. 이들은 속성에 지정된 표현을 변경합니다. 지정된 표현이 없으면 기본 표현이 변경됩니다.
#![allow(unused)]
fn main() {
// 기본 표현, 정렬이 2로 낮아짐.
#[repr(packed(2))]
struct PackedStruct {
first: i16,
second: i8,
third: i32
}
// C 표현, 정렬이 8로 높아짐
#[repr(C, align(8))]
struct AlignedStruct {
first: i16,
second: i8,
third: i32
}
}
Note
As a consequence of the representation being an attribute on the item, the representation does not depend on generic parameters. Any two types with the same name have the same representation. For example,
Foo<Bar>andFoo<Baz>both have the same representation.
타입의 표현은 필드 간의 패딩을 변경할 수 있지만, 필드 자체의 레이아웃은 변경하지 않습니다. 예를 들어, Rust 표현을 가진 Inner 구조체를 포함하는 C 표현의 구조체는 Inner 의 레이아웃을 변경하지 않습니다.
The Rust representation
Rust 표현은 repr 속성이 없는 명목적 타입의 기본 표현입니다. repr 속성을 통해 이 표현을 명시적으로 사용하는 것은 속성을 완전히 생략하는 것과 동일함이 보장됩니다.
이 표현이 제공하는 유일한 데이터 레이아웃 보장은 안전성(soundness)을 위해 필요한 것들뿐입니다. 그 보장들은 다음과 같습니다:
- 필드들이 올바르게 정렬됩니다.
- 필드들이 겹치지 않습니다.
- 타입의 정렬은 최소한 그 필드들의 최대 정렬 이상입니다.
형식적으로, 첫 번째 보장은 모든 필드의 오프셋이 해당 필드의 정렬로 나누어떨어진다는 것을 의미합니다.
두 번째 보장은 필드들이 순서대로 정렬될 때, 어떤 필드의 오프셋 더하기 크기가 다음 필드의 오프셋보다 작거나 같도록 정렬될 수 있음을 의미합니다. 이 순서는 타입 선언에 필드가 지정된 순서와 동일할 필요는 없습니다.
두 번째 보장이 필드들이 서로 다른 주소를 갖는다는 것을 의미하지는 않음에 유의하십시오. 크기가 0인 타입은 동일한 구조체 내의 다른 필드와 같은 주소를 가질 수 있습니다.
이 표현이 제공하는 데이터 레이아웃에 대한 다른 보장은 없습니다.
The C representation
C 표현은 이중 목적으로 설계되었습니다. 첫 번째 목적은 C 언어와 상호 운용 가능한 타입을 생성하는 것입니다. 두 번째 목적은 값을 다른 타입으로 재해석하는 것과 같이 데이터 레이아웃에 의존하는 연산을 안전하게 수행할 수 있는 타입을 생성하는 것입니다.
이러한 이중 목적 때문에, C 프로그래밍 언어와의 인터페이스에는 유용하지 않은 타입을 생성하는 것도 가능합니다.
이 표현은 구조체, 공용체, 열거형에 적용될 수 있습니다. 예외는 변형이 없는 열거형 으로, 이에 대해 C 표현을 사용하는 것은 오류입니다.
#[repr(C)] 구조체
구조체의 정렬은 그 안에 있는 가장 크게 정렬된 필드의 정렬입니다.
필드의 크기와 오프셋은 다음 알고리즘에 의해 결정됩니다.
현재 오프셋 0바이트로 시작합니다.
구조체의 선언 순서대로 각 필드에 대해, 먼저 필드의 크기와 정렬을 결정합니다. 현재 오프셋이 필드의 정렬의 배수가 아니라면, 필드 정렬의 배수가 될 때까지 현재 오프셋에 패딩 바이트를 추가합니다. 해당 필드의 오프셋은 현재 오프셋 값이 됩니다. 그런 다음 현재 오프셋을 필드의 크기만큼 증가시킵니다.
마지막으로, 구조체의 크기는 현재 오프셋을 구조체의 정렬의 가장 가까운 배수로 올림한 값입니다.
다음은 의사코드로 설명된 이 알고리즘입니다.
/// 다음 주소가 `alignment` 에 맞춰 정렬되도록 하기 위해
/// `offset` 뒤에 필요한 패딩의 양을 반환합니다.
fn padding_needed_for(offset: usize, alignment: usize) -> usize {
let misalignment = offset % alignment;
if misalignment > 0 {
// `alignment` 의 다음 배수로 올림
alignment - misalignment
} else {
// 이미 `alignment` 의 배수임
0
}
}
struct.alignment = struct.fields().map(|field| field.alignment).max();
let current_offset = 0;
for field in struct.fields_in_declaration_order() {
// 현재 오프셋을 증가시켜 이 필드의 정렬의 배수가 되도록 합니다.
// 첫 번째 필드의 경우, 이는 항상 0이 됩니다.
// 건너뛴 바이트를 패딩 바이트라고 합니다.
current_offset += padding_needed_for(current_offset, field.alignment);
struct[field].offset = current_offset;
current_offset += field.size;
}
struct.size = current_offset + padding_needed_for(current_offset, struct.alignment);
Warning
This pseudocode uses a naive algorithm that ignores overflow issues for the sake of clarity. To perform memory layout computations in actual code, use
Layout.
Note
This algorithm can produce zero-sized structs. In C, an empty struct declaration like
struct Foo { }is illegal. However, both gcc and clang support options to enable such structs, and assign them size zero. C++, in contrast, gives empty structs a size of 1, unless they are inherited from or they are fields that have the[[no_unique_address]]attribute, in which case they do not increase the overall size of the struct.
#[repr(C)] 공용체
#[repr(C)] 로 선언된 공용체는 대상 플랫폼의 C 언어에서 동등한 C 공용체 선언과 동일한 크기와 정렬을 가집니다.
The union will have a size of the maximum size of all of its fields rounded to its alignment, and an alignment of the maximum alignment of all of its fields. These maximums may come from different fields. Each field lives at byte offset 0 from the beginning of the union.
#![allow(unused)]
fn main() {
#[repr(C)]
union Union {
f1: u16,
f2: [u8; 4],
}
assert_eq!(std::mem::size_of::<Union>(), 4); // f2에서 옴
assert_eq!(std::mem::align_of::<Union>(), 2); // f1에서 옴
assert_eq!(std::mem::offset_of!(Union, f1), 0);
assert_eq!(std::mem::offset_of!(Union, f2), 0);
#[repr(C)]
union SizeRoundedUp {
a: u32,
b: [u16; 3],
}
assert_eq!(std::mem::size_of::<SizeRoundedUp>(), 8); // b에서 크기 6,
// a의 정렬에 따라
// 8로 올림.
assert_eq!(std::mem::align_of::<SizeRoundedUp>(), 4); // a에서 옴
assert_eq!(std::mem::offset_of!(SizeRoundedUp, a), 0);
assert_eq!(std::mem::offset_of!(SizeRoundedUp, b), 0);
}
#[repr(C)] 필드 없는 열거형
필드 없는 열거형 의 경우, C 표현은 대상 플랫폼의 C ABI에 대한 기본 enum 크기 및 정렬과 동일한 크기 및 정렬을 가집니다.
Note
The enum representation in C is implementation defined, so this is really a “best guess”. In particular, this may be incorrect when the C code of interest is compiled with certain flags.
Warning
There are crucial differences between an
enumin the C language and Rust’s field-less enums with this representation. Anenumin C is mostly atypedefplus some named constants; in other words, an object of anenumtype can hold any integer value. For example, this is often used for bitflags inC. In contrast, Rust’s field-less enums can only legally hold the discriminant values, everything else is undefined behavior. Therefore, using a field-less enum in FFI to model a Cenumis often wrong.
#[repr(C)] 필드 있는 열거형
필드가 있는 repr(C) 열거형의 표현은 두 개의 필드를 가진 repr(C) 구조체이며, C에서는 “태그된 공용체(tagged union)“라고도 불립니다:
- 모든 필드가 제거된 열거형의
repr(C)버전 (“태그”)
- 필드를 가진 각 변형의 필드들을 위한
repr(C)구조체들의repr(C)공용체 (“페이로드”)
Note
Due to the representation of
repr(C)structs and unions, if a variant has a single field there is no difference between putting that field directly in the union or wrapping it in a struct; any system which wishes to manipulate such anenum’s representation may therefore use whichever form is more convenient or consistent for them.
#![allow(unused)]
fn main() {
// 이 Enum은 다음 구조체와 동일한 표현을 가집니다 ...
#[repr(C)]
enum MyEnum {
A(u32),
B(f32, u64),
C { x: u32, y: u8 },
D,
}
// ... 이 구조체와.
#[repr(C)]
struct MyEnumRepr {
tag: MyEnumDiscriminant,
payload: MyEnumFields,
}
// 이것은 판별자 열거형입니다.
#[repr(C)]
enum MyEnumDiscriminant { A, B, C, D }
// 이것은 변형 공용체입니다.
#[repr(C)]
union MyEnumFields {
A: MyAFields,
B: MyBFields,
C: MyCFields,
D: MyDFields,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct MyAFields(u32);
#[repr(C)]
#[derive(Copy, Clone)]
struct MyBFields(f32, u64);
#[repr(C)]
#[derive(Copy, Clone)]
struct MyCFields { x: u32, y: u8 }
// 이 구조체는 생략될 수 있으며(0크기 타입임), C/C++ 헤더에 있어야 합니다.
#[repr(C)]
#[derive(Copy, Clone)]
struct MyDFields;
}
기본 표현 (Primitive representations)
기본 표현 은 기본 정수 타입과 동일한 이름을 가진 표현입니다. 즉: u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize 입니다.
기본 표현은 열거형에만 적용될 수 있으며 열거형에 필드가 있는지 없는지에 따라 다르게 동작합니다. 변형이 없는 열거형 이 기본 표현을 갖는 것은 오류입니다. 두 개의 기본 표현을 결합하는 것은 오류입니다.
Primitive representation of field-less enums
필드 없는 열거형 의 경우, 기본 표현은 크기와 정렬을 동일한 이름의 기본 타입과 같게 설정합니다. 예를 들어, u8 표현을 가진 필드 없는 열거형은 0에서 255(포함) 사이의 판별자만 가질 수 있습니다.
Primitive representation of enums with fields
기본 표현 열거형의 표현은 repr(C) 공용체입니다
Note
This representation is unchanged if the tag is given its own member in the union, should that make manipulation more clear for you (although to follow the C++ standard the tag member should be wrapped in a
struct).
#![allow(unused)]
fn main() {
// 이 열거형은 다음 공용체와 동일한 표현을 가집니다 ...
#[repr(u8)]
enum MyEnum {
A(u32),
B(f32, u64),
C { x: u32, y: u8 },
D,
}
// ... 이 공용체와.
#[repr(C)]
union MyEnumRepr {
A: MyVariantA,
B: MyVariantB,
C: MyVariantC,
D: MyVariantD,
}
// 이것은 판별자 열거형입니다.
#[repr(u8)]
#[derive(Copy, Clone)]
enum MyEnumDiscriminant { A, B, C, D }
#[repr(C)]
#[derive(Clone, Copy)]
struct MyVariantA(MyEnumDiscriminant, u32);
#[repr(C)]
#[derive(Clone, Copy)]
struct MyVariantB(MyEnumDiscriminant, f32, u64);
#[repr(C)]
#[derive(Clone, Copy)]
struct MyVariantC { tag: MyEnumDiscriminant, x: u32, y: u8 }
#[repr(C)]
#[derive(Clone, Copy)]
struct MyVariantD(MyEnumDiscriminant);
}
필드가 있는 열거형의 기본 표현과 #[repr(C)] 결합하기
필드가 있는 열거형의 경우, repr(C) 와 기본 표현을 결합하는 것도 가능합니다(예: repr(C, u8)). 이는 판별자 열거형의 표현을 선택된 기본 타입으로 변경함으로써 repr(C) 를 수정합니다. 따라서 u8 표현을 선택했다면, 판별자 열거형은 1바이트의 크기와 정렬을 갖게 됩니다.
앞서 나온 예시의 판별자 열거형은 다음과 같이 됩니다:
#![allow(unused)]
fn main() {
#[repr(C, u8)] // `u8` 이 추가됨
enum MyEnum {
A(u32),
B(f32, u64),
C { x: u32, y: u8 },
D,
}
// ...
#[repr(u8)] // 따라서 여기서 `C` 대신 `u8` 이 사용됨
enum MyEnumDiscriminant { A, B, C, D }
// ...
}
예를 들어, repr(C, u8) 열거형은 257개의 고유한 판별자(“태그”)를 가질 수 없는 반면, repr(C) 속성만 있는 동일한 열거형은 문제없이 컴파일됩니다.
repr(C) 에 더해 기본 표현을 사용하면 repr(C) 형태로부터 열거형의 크기가 변경될 수 있습니다:
#![allow(unused)]
fn main() {
#[repr(C)]
enum EnumC {
Variant0(u8),
Variant1,
}
#[repr(C, u8)]
enum Enum8 {
Variant0(u8),
Variant1,
}
#[repr(C, u16)]
enum Enum16 {
Variant0(u8),
Variant1,
}
// The size of the C representation is platform dependent
assert_eq!(std::mem::size_of::<EnumC>(), 8);
// 판별자를 위한 1바이트와 Enum8::Variant0의 값을 위한 1바이트
assert_eq!(std::mem::size_of::<Enum8>(), 2);
// 판별자를 위한 2바이트와 Enum16::Variant0의 값을 위한 1바이트
// 더하기 1바이트의 패딩.
assert_eq!(std::mem::size_of::<Enum16>(), 4);
}
정렬 수정자
align 과 packed 수정자는 각각 struct 와 union 의 정렬을 높이거나 낮출 때 사용할 수 있습니다. packed 는 필드 사이의 패딩을 변경할 수도 있습니다(단, 필드 내부의 패딩은 변경하지 않습니다). align 과 packed 는 그 자체로는 구조체 레이아웃이나 열거형 변형 레이아웃의 필드 순서에 대한 보장을 제공하지 않지만, 그러한 보장을 제공하는 표현(예: C)과 결합될 수 있습니다.
The alignment is specified as an integer parameter in the form of #[repr(align(x))] or #[repr(packed(x))]. The alignment value must be a power of two from 1 up to 229. For packed, if no value is given, as in #[repr(packed)], then the value is 1.
align 의 경우, 지정된 정렬이 align 수정자가 없을 때의 타입 정렬보다 작으면 정렬은 영향을 받지 않습니다.
packed 의 경우, 지정된 정렬이 packed 수정자가 없을 때의 타입 정렬보다 크면 정렬과 레이아웃은 영향을 받지 않습니다.
필드 배치를 위한 각 필드의 정렬은 지정된 정렬과 필드 타입의 정렬 중 더 작은 값입니다.
필드 간 패딩은 각 필드의 (변경되었을 수 있는) 정렬을 충족하기 위해 필요한 최소한의 양으로 보장됩니다(단, packed 자체는 필드 순서에 대한 어떤 보장도 제공하지 않음에 유의하십시오). 이 규칙의 중요한 결과는 #[repr(packed(1))](또는 #[repr(packed)])을 가진 타입은 필드 간 패딩이 없다는 것입니다.
align 과 packed 수정자는 동일한 타입에 적용될 수 없으며, packed 타입은 전이적으로 다른 align 된 타입을 포함할 수 없습니다. align 과 packed 는 Rust 및 C 표현에만 적용될 수 있습니다.
align 수정자는 enum 에도 적용될 수 있습니다. 이 경우, enum 의 정렬에 미치는 효과는 enum 이 동일한 align 수정자를 가진 뉴타입 struct 로 감싸진 경우와 동일합니다.
Note
References to unaligned fields are not allowed because it is undefined behavior. When fields are unaligned due to an alignment modifier, consider the following options for using references and dereferences:
#![allow(unused)] fn main() { #[repr(packed)] struct Packed { f1: u8, f2: u16, } let mut e = Packed { f1: 1, f2: 2 }; // 필드에 대한 참조를 생성하는 대신, 값을 로컬 변수로 복사하십시오. let x = e.f2; // 또는 참조를 생성하는 `println!` 과 같은 상황에서는 중괄호를 사용하여 // 값의 복사본으로 변경하십시오. println!("{}", {e.f2}); // 포인터가 필요한 경우, 포인터를 직접 역참조하는 대신 // 읽기 및 쓰기에 정렬되지 않은 메서드를 사용하십시오. let ptr: *const u16 = &raw const e.f2; let value = unsafe { ptr.read_unaligned() }; let mut_ptr: *mut u16 = &raw mut e.f2; unsafe { mut_ptr.write_unaligned(3) } }
The transparent representation
transparent 표현은 다음을 가진 struct 또는 단일 변형 enum 에서만 사용될 수 있습니다:
- 크기가 0이고 정렬이 1인 임의 개수의 필드 (예:
PhantomData<T>), 그리고 - 최대 하나의 다른 필드.
이 표현을 가진 구조체와 열거형은 크기가 0이 아니고 정렬이 1이 아닌 유일한 필드(존재하는 경우)와 동일한 레이아웃 및 ABI를 가지며, 그렇지 않으면 유닛과 동일합니다.
이는 C 표현과는 다릅니다. C 표현을 가진 구조체는 항상 C struct 의 ABI를 갖는 반면, 예를 들어 기본(primitive) 필드를 가진 transparent 표현의 구조체는 해당 기본 필드의 ABI를 갖기 때문입니다.
이 표현은 타입 레이아웃을 다른 타입에 위임하므로, 다른 어떤 표현과도 함께 사용될 수 없습니다.
내부 가변성
때로는 여러 별칭(aliases)을 가진 상태에서 타입을 변경해야 할 때가 있습니다. 러스트에서는 내부 가변성 이라는 패턴을 사용하여 이를 달성합니다.
타입에 대한 공유 참조 를 통해 내부 상태를 변경할 수 있다면 그 타입은 내부 가변성을 가집니다.
이는 공유 참조가 가리키는 값은 변경되지 않는다는 일반적인 요구사항 에 반하는 것입니다.
std::cell::UnsafeCell<T> 타입은 이 요구사항을 비활성화하는 유일한 허용된 방법입니다. UnsafeCell<T> 가 불변으로 별칭이 지정되어 있더라도, 그 안에 포함된 T 를 변경하거나 T 에 대한 가변 참조를 얻는 것은 여전히 안전합니다.
다른 모든 타입과 마찬가지로, 여러 개의 &mut UnsafeCell<T> 별칭을 갖는 것은 정의되지 않은 동작입니다.
내부 가변성을 가진 다른 타입들은 UnsafeCell<T> 를 필드로 사용하여 생성될 수 있습니다. 표준 라이브러리는 안전한 내부 가변성 API를 제공하는 다양한 타입을 제공합니다.
예를 들어, std::cell::RefCell<T> 은 런타임 차용 검사를 사용하여 다중 참조에 대한 일반적인 규칙을 보장합니다.
std::sync::atomic 모듈은 원자적 연산으로만 접근되는 값을 감싸는 타입을 포함하여, 값이 스레드 간에 공유되고 변경될 수 있도록 합니다.
Subtyping and variance
서브타이핑은 암시적이며 타입 검사나 추론의 어느 단계에서든 발생할 수 있습니다.
서브타이핑은 두 가지 경우로 제한됩니다: 라이프타임에 대한 가변성(variance)과 고차원(higher ranked) 라이프타임을 가진 타입 간의 관계입니다. 만약 타입에서 라이프타임을 제거한다면, 유일한 서브타이핑은 타입 동등성 때문일 것입니다.
다음 예시를 고려해보십시오: 문자열 리터럴은 항상 'static 라이프타임을 가집니다. 그럼에도 불구하고 우리는 s 를 t 에 할당할 수 있습니다:
#![allow(unused)]
fn main() {
fn bar<'a>() {
let s: &'static str = "hi";
let t: &'a str = s;
}
}
'static 은 라이프타임 매개변수 'a 보다 오래 생존하므로, &'static str 은 &'a str 의 서브타입입니다.
고차원(Higher-ranked) 함수 포인터 와 트레잇 객체 는 또 다른 서브타입 관계를 가집니다. 이들은 고차원 라이프타임의 대체로 주어지는 타입들의 서브타입입니다. 몇 가지 예시:
#![allow(unused)]
fn main() {
// 여기서 'a는 'static으로 대체됩니다
let subtype: &(for<'a> fn(&'a i32) -> &'a i32) = &((|x| x) as fn(&_) -> &_);
let supertype: &(fn(&'static i32) -> &'static i32) = subtype;
// 이는 트레잇 객체에 대해서도 유사하게 작동합니다
let subtype: &(dyn for<'a> Fn(&'a i32) -> &'a i32) = &|x| x;
let supertype: &(dyn Fn(&'static i32) -> &'static i32) = subtype;
// 하나의 고차원 라이프타임을 다른 것으로 대체할 수도 있습니다
let subtype: &(for<'a, 'b> fn(&'a i32, &'b i32)) = &((|x, y| {}) as fn(&_, &_));
let supertype: &for<'c> fn(&'c i32, &'c i32) = subtype;
}
가변성 (Variance)
가변성은 제네릭 타입이 그 인자에 대해 갖는 속성입니다. 매개변수에 대한 제네릭 타입의 가변성 은 매개변수의 서브타이핑이 타입의 서브타이핑에 어떻게 영향을 미치는지를 나타냅니다.
T가U의 서브타입일 때F<T>가F<U>의 서브타입이면,F<T>는T에 대해 공변적(covariant) 입니다 (서브타이핑이 “통과“함).
T가U의 서브타입일 때F<U>가F<T>의 서브타입이면,F<T>는T에 대해 반변적(contravariant) 입니다.
- 그렇지 않으면
F<T>는T에 대해 불변적(invariant) 입니다 (서브타입 관계가 유도될 수 없음)
타입의 가변성은 다음과 같이 자동으로 결정됩니다
| 유형 | 'a 에 대한 가변성 | T 에 대한 가변성 |
|---|---|---|
&'a T | 공변적 | 공변적 |
&'a mut T | 공변적 | 불변적 |
*const T | 공변적 | |
*mut T | 불변적 | |
[T] 및 [T; n] | 공변적 | |
fn() -> T | 공변적 | |
fn(T) -> () | 반변적 | |
std::cell::UnsafeCell<T> | 불변적 | |
std::marker::PhantomData<T> | 공변적 | |
dyn Trait<T> + 'a | 공변적 | 불변적 |
다른 struct, enum, union 타입의 가변성은 필드 타입의 가변성을 보고 결정됩니다. 매개변수가 서로 다른 가변성을 가진 위치에서 사용되면 해당 매개변수는 불변적입니다. 예를 들어, 다음 구조체는 'a 와 T 에 대해 공변적이며, 'b, 'c, U 에 대해서는 불변적입니다.
#![allow(unused)]
fn main() {
use std::cell::UnsafeCell;
struct Variance<'a, 'b, 'c, T, U: 'a> {
x: &'a U, // 이것은 `Variance` 를 'a에 대해 공변적으로 만들고,
// U에 대해서도 공변적으로 만들겠지만, U는 나중에 사용됩니다
y: *const T, // T에 대해 공변적
z: UnsafeCell<&'b f64>, // 'b에 대해 불변적
w: *mut U, // U에 대해 불변적이며, 전체 구조체를 불변적으로 만듦
f: fn(&'c ()) -> &'c () // 공변적이면서 동시에 반변적이므로, 구조체 내에서 'c를 불변적으로 만듭니다.
}
}
struct, enum, union 외부에서 사용될 때, 매개변수의 가변성은 각 위치에서 개별적으로 확인됩니다.
#![allow(unused)]
fn main() {
use std::cell::UnsafeCell;
fn generic_tuple<'short, 'long: 'short>(
// 'long은 튜플 내부의 공변적 위치와 불변적 위치 모두에서 사용됩니다.
x: (&'long u32, UnsafeCell<&'long u32>),
) {
// 이 위치들에서의 가변성은 개별적으로 계산되므로,
// 공변적 위치에서 'long을 자유롭게 줄일 수 있습니다.
let _: (&'short u32, UnsafeCell<&'long u32>) = x;
}
fn takes_fn_ptr<'short, 'middle: 'short>(
// 'middle은 공변적 위치와 반변적 위치 모두에서 사용됩니다.
f: fn(&'middle ()) -> &'middle (),
) {
// 이 위치들에서의 가변성은 개별적으로 계산되므로,
// 공변적 위치에서 'middle을 자유롭게 줄일 수 있고
// 반변적 위치에서 늘릴 수 있습니다.
let _: fn(&'static ()) -> &'short () = f;
}
}
트레잇과 라이프타임 바운드
Syntax
TypeParamBounds → TypeParamBound ( + TypeParamBound )* +?
TypeParamBound → Lifetime | TraitBound | UseBound
TraitBound →
( ? | ForLifetimes )? TypePath
| ( ( ? | ForLifetimes )? TypePath )
LifetimeBounds → ( Lifetime + )* Lifetime?
Lifetime →
LIFETIME_OR_LABEL
| ‘static
| ’_
UseBound → use UseBoundGenericArgs
UseBoundGenericArgs →
< >
| < ( UseBoundGenericArg , )* UseBoundGenericArg ,? >
UseBoundGenericArg →
Lifetime
| IDENTIFIER
| Self
트레잇 및 라이프타임 바운드는 제네릭 아이템 이 매개변수로 사용될 수 있는 타입과 라이프타임을 제한하는 방법을 제공합니다. 바운드는 where 절 의 모든 타입에 대해 제공될 수 있습니다. 특정 일반적인 경우에 대한 더 짧은 형식도 있습니다:
- 제네릭 매개변수 선언 뒤에 작성된 바운드:
fn f<A: Copy>() {}는fn f<A>() where A: Copy {}와 같습니다. - 트레잇 선언에서 상위 트레잇(supertraits) 으로서:
trait Circle : Shape {}는trait Circle where Self : Shape {}와 동일합니다. - 트레잇 선언에서 연관 타입 에 대한 바운드로서:
trait A { type B: Copy; }는trait A where Self::B: Copy { type B; }와 동일합니다.
아이템의 바운드는 아이템을 사용할 때 만족되어야 합니다. 제네릭 아이템을 타입 검사하고 차용 검사할 때, 바운드는 어떤 타입에 대해 트레잇이 구현되었는지를 판단하는 데 사용될 수 있습니다. 예를 들어, Ty: Trait 가 주어졌을 때
- 제네릭 함수의 본문에서,
Trait의 메서드를Ty값에 대해 호출할 수 있습니다. 마찬가지로Trait의 연관 상수도 사용할 수 있습니다. Trait의 연관 타입을 사용할 수 있습니다.T: Trait바운드가 있는 제네릭 함수 및 타입은T대신Ty를 사용하여 사용될 수 있습니다.
#![allow(unused)]
fn main() {
type Surface = i32;
trait Shape {
fn draw(&self, surface: Surface);
fn name() -> &'static str;
}
fn draw_twice<T: Shape>(surface: Surface, sh: T) {
sh.draw(surface); // T: Shape이므로 메서드 호출 가능
sh.draw(surface);
}
fn copy_and_draw_twice<T: Copy>(surface: Surface, sh: T) where T: Shape {
let shape_copy = sh; // T: Copy이므로 sh를 이동시키지 않음
draw_twice(surface, sh); // T: Shape이므로 제네릭 함수 사용 가능
}
struct Figure<S: Shape>(S, S);
fn name_figure<U: Shape>(
figure: Figure<U>, // U: Shape이므로 타입 Figure<U>는 잘 형성됨(well-formed)
) {
println!(
"두 개의 {} 모양",
U::name(), // 연관 함수 사용 가능
);
}
}
아이템의 매개변수나 고차원 라이프타임 을 사용하지 않는 바운드는 아이템이 정의될 때 검사됩니다. 그러한 바운드가 거짓이면 오류입니다.
Copy, Clone, and Sized bounds are also checked for certain generic types when using the item, even if the use does not provide a concrete type. It is an error to have Copy or Clone as a bound on a mutable reference, trait object, or slice. It is an error to have Sized as a bound on a trait object or slice.
#![allow(unused)]
fn main() {
struct A<'a, T>
where
i32: Default, // 허용되지만 유용하지는 않음
i32: Iterator, // 오류: `i32` 는 반복자가 아님
&'a mut T: Copy, // (사용 시) 오류: 트레잇 바운드가 만족되지 않음
[T]: Sized, // (사용 시) 오류: 컴파일 시에 크기를 알 수 없음
{
f: &'a T,
}
struct UsesA<'a, T>(A<'a, T>);
}
트레잇 및 라이프타임 바운드는 트레잇 객체 를 명명하는 데에도 사용됩니다.
?Sized
? is only used to relax the implicit Sized trait bound for type parameters or associated types. ?Sized may not be used as a bound for other types.
라이프타임 바운드
라이프타임 바운드는 타입이나 다른 라이프타임에 적용될 수 있습니다.
'a: 'b 바운드는 보통 'a 가 'b 보다 오래 산다(outlives) 라고 읽습니다. 'a: 'b 는 'a 가 적어도 'b 만큼 오래 지속됨을 의미하므로, &'b () 가 유효할 때마다 &'a () 참조도 유효합니다.
#![allow(unused)]
fn main() {
fn f<'a, 'b>(x: &'a i32, mut y: &'b i32) where 'a: 'b {
y = x; // 'a: 'b이므로 &'a i32는 &'b i32의 서브타입입니다
let r: &'b &'a i32 = &&0; // 'a: 'b이므로 &'b &'a i32는 잘 형성되었습니다
}
}
T: 'a 는 T 의 모든 라이프타임 매개변수가 'a 보다 오래 산다는 것을 의미합니다. 예를 들어, 'a 가 제약 없는 라이프타임 매개변수라면 i32: 'static 과 &'static str: 'a 는 만족되지만, Vec<&'a ()>: 'static 은 만족되지 않습니다.
고차원 트레잇 바운드
Syntax
ForLifetimes → for GenericParams
트레잇 바운드는 라이프타임에 대해 고차원(higher ranked) 일 수 있습니다. 이 바운드들은 모든 라이프타임에 대해 참인 바운드를 지정합니다. 예를 들어, for<'a> &'a T: PartialEq<i32> 와 같은 바운드는 다음과 같은 구현을 요구합니다.
#![allow(unused)]
fn main() {
struct T;
impl<'a> PartialEq<i32> for &'a T {
// ...
fn eq(&self, other: &i32) -> bool {true}
}
}
그러면 어떤 라이프타임을 가진 &'a T 라도 i32 와 비교하는 데 사용될 수 있습니다.
여기서는 고차원 바운드만 사용될 수 있는데, 이는 참조의 라이프타임이 함수의 모든 가능한 라이프타임 매개변수보다 짧기 때문입니다.
#![allow(unused)]
fn main() {
fn call_on_ref_zero<F>(f: F) where for<'a> F: Fn(&'a i32) {
let zero = 0;
f(&zero);
}
}
고차원 라이프타임은 트레잇 바로 앞에서 지정될 수도 있습니다. 유일한 차이점은 라이프타임 매개변수의 스코프(scope) 이며, 이는 전체 바운드가 아닌 뒤따르는 트레잇의 끝까지만 확장됩니다. 이 함수는 이전 함수와 동일합니다.
#![allow(unused)]
fn main() {
fn call_on_ref_zero<F>(f: F) where F: for<'a> Fn(&'a i32) {
let zero = 0;
f(&zero);
}
}
함축된 바운드
타입이 잘 형성되기 위해 필요한 라이프타임 바운드는 때때로 추론됩니다.
#![allow(unused)]
fn main() {
fn requires_t_outlives_a<'a, T>(x: &'a T) {}
}
타입 &'a T 가 잘 형성되기 위해서는 타입 매개변수 T 가 'a 보다 오래 살아야 합니다. 이는 함수 시그니처에 T: 'a 가 성립해야만 유효한 &'a T 타입이 포함되어 있기 때문에 추론됩니다.
함수의 모든 매개변수와 출력에 대해 함축된 바운드가 추가됩니다. requires_t_outlives_a 내부에서는 명시적으로 지정하지 않더라도 T: 'a 가 성립한다고 가정할 수 있습니다.
#![allow(unused)]
fn main() {
fn requires_t_outlives_a_not_implied<'a, T: 'a>() {}
fn requires_t_outlives_a<'a, T>(x: &'a T) {
// 이 코드는 컴파일됩니다. 참조 타입 `&'a T` 에 의해
// `T: 'a` 가 함축되기 때문입니다.
requires_t_outlives_a_not_implied::<'a, T>();
}
}
#![allow(unused)]
fn main() {
fn requires_t_outlives_a_not_implied<'a, T: 'a>() {}
fn not_implied<'a, T>() {
// 이 코드는 오류가 발생합니다. 함수 시그니처에 의해
// `T: 'a` 가 함축되지 않기 때문입니다.
requires_t_outlives_a_not_implied::<'a, T>();
}
}
라이프타임 바운드만 함축될 뿐이며, 트레잇 바운드는 여전히 명시적으로 추가해야 합니다. 따라서 다음 예제는 오류를 발생시킵니다.
#![allow(unused)]
fn main() {
use std::fmt::Debug;
struct IsDebug<T: Debug>(T);
// error[E0277]: `T` 가 `Debug` 를 구현하지 않음
fn doesnt_specify_t_debug<T>(x: IsDebug<T>) {}
}
라이프타임 바운드는 타입 정의 및 모든 타입에 대한 impl 블록에서도 추론됩니다.
#![allow(unused)]
fn main() {
struct Struct<'a, T> {
// 이것이 잘 형성되기 위해서는 `T: 'a` 가 필요하며,
// 이는 컴파일러에 의해 추론됩니다.
field: &'a T,
}
enum Enum<'a, T> {
// 이것이 잘 형성되기 위해서는 `T: 'a` 가 필요하며,
// 이는 컴파일러에 의해 추론됩니다.
//
// `Enum::OtherVariant` 만 사용하는 경우에도
// `T: 'a` 가 필요함에 유의하세요.
SomeVariant(&'a T),
OtherVariant,
}
trait Trait<'a, T: 'a> {}
// 이 코드는 오류가 발생합니다. impl 헤더의 어떤 타입에 의해서도
// `T: 'a` 가 함축되지 않기 때문입니다.
// impl<'a, T> Trait<'a, T> for () {}
// 이 코드는 컴파일됩니다. 셀프 타입 `&'a T` 에 의해 `T: 'a` 가 함축되기 때문입니다.
impl<'a, T> Trait<'a, T> for &'a T {}
}
사용 바운드
일부 바운드 목록에는 impl Trait 추상 반환 타입(abstract return type) 에 의해 캡처되는 제네릭 매개변수를 제어하기 위해 use<..> 바운드가 포함될 수 있습니다. 자세한 내용은 정밀한 캡처(precise capturing) 를 참조하세요.
타입 강제 변환
타입 강제 변환(Type coercions) 은 값의 타입을 변경하는 암시적 연산입니다. 프로그램의 특정 위치에서 자동으로 발생하며, 실제로 강제 변환이 가능한 타입은 엄격히 제한됩니다.
강제 변환에 의해 허용되는 모든 변환은 타입 캐스트 연산자(type cast operator) 인 as 를 통해 명시적으로 수행될 수도 있습니다.
강제 변환은 원래 RFC 401 에서 정의되었으며, RFC 1558 에서 확장되었습니다.
강제 변환 지점
강제 변환은 프로그램의 특정 강제 변환 지점에서만 발생할 수 있습니다. 이러한 지점은 일반적으로 원하는 타입이 명시되어 있거나, 명시적인 타입으로부터 (타입 추론 없이) 전파되어 유도될 수 있는 곳입니다. 가능한 강제 변환 지점은 다음과 같습니다.
-
명시적인 타입이 주어진
let구문.예를 들어, 다음 코드에서
&mut 42는&i8타입으로 강제 변환됩니다.#![allow(unused)] fn main() { let _: &i8 = &mut 42; }
static및const아이템 선언 (let구문과 유사함).
-
함수 호출 인자
강제 변환되는 값은 실제 매개변수(actual parameter)이며, 이는 형식 매개변수(formal parameter)의 타입으로 강제 변환됩니다.
예를 들어, 다음 코드에서
&mut 42는&i8타입으로 강제 변환됩니다.fn bar(_: &i8) { } fn main() { bar(&mut 42); }메서드 호출의 경우, 리시버(
self매개변수) 타입은 다르게 강제 변환됩니다. 자세한 내용은 메서드 호출 표현식(method-call expressions) 문서를 참조하세요.
-
구조체, 공용체, 또는 열거형 변형 필드의 인스턴스화
예를 들어, 다음 코드에서
&mut 42는&i8타입으로 강제 변환됩니다.struct Foo<'a> { x: &'a i8 } fn main() { Foo { x: &mut 42 }; }
-
함수 결과 — 세미콜론으로 끝나지 않은 블록의 마지막 줄 또는
return구문의 모든 표현식예를 들어, 다음 코드에서
x는&dyn Display타입으로 강제 변환됩니다.#![allow(unused)] fn main() { use std::fmt::Display; fn foo(x: &u32) -> &dyn Display { x } }
-
Assigned value operands in assignment expressions
For example,
yis coerced to have type&i8in the following:#![allow(unused)] fn main() { let mut x = &0i8; let y = &mut 42i8; x = y; }
만약 이러한 강제 변환 지점 중 하나에 있는 표현식이 강제 변환 전파 표현식인 경우, 해당 표현식의 관련 하위 표현식들 또한 강제 변환 지점이 됩니다. 전파는 이러한 새로운 강제 변환 지점들로부터 재귀적으로 발생합니다. 전파 표현식과 그와 관련된 하위 표현식들은 다음과 같습니다.
- 배열 리터럴 (배열의 타입이
[U; n]인 경우). 배열 리터럴의 각 하위 표현식은U타입으로의 강제 변환을 위한 강제 변환 지점입니다.
- 반복 구문을 사용하는 배열 리터럴 (배열의 타입이
[U; n]인 경우). 반복되는 하위 표현식은U타입으로의 강제 변환을 위한 강제 변환 지점입니다.
- 튜플 (튜플이
(U_0, U_1, ..., U_n)타입으로의 강제 변환 지점인 경우). 각 하위 표현식은 해당 타입으로의 강제 변환 지점입니다. 예를 들어, 0번째 하위 표현식은U_0타입으로의 강제 변환 지점입니다.
- 괄호로 묶인 하위 표현식 (
(e)): 표현식의 타입이U인 경우, 하위 표현식은U로의 강제 변환 지점입니다.
- 블록: 블록의 타입이
U인 경우, 블록의 마지막 표현식(세미콜론으로 끝나지 않은 경우)은U로의 강제 변환 지점입니다. 여기에는 블록이 알려진 타입을 가진 경우if/else와 같은 제어 흐름 구문의 일부인 블록도 포함됩니다.
강제 변환 타입
강제 변환은 다음 타입들 사이에서 허용됩니다.
T가U의 서브타입(subtype) 인 경우T에서U로 (반사적 사례)
-
T_1이T_2로 강제 변환되고T_2가T_3으로 강제 변환되는 경우T_1에서T_3으로 (_ 이행적 사례_)이는 아직 완전히 지원되지 않음에 유의하세요.
&mut T에서&T로
*mut T에서*const T로
&T에서*const T로
&mut T에서*mut T로
-
T가Deref<Target = U>를 구현하는 경우&T또는&mut T에서&U로. 예를 들어:use std::ops::Deref; struct CharContainer { value: char, } impl Deref for CharContainer { type Target = char; fn deref<'a>(&'a self) -> &'a char { &self.value } } fn foo(arg: &char) {} fn main() { let x = &mut CharContainer { value: 'y' }; foo(x); //&mut CharContainer가 &char로 강제 변환됩니다. }
T가DerefMut<Target = U>를 구현하는 경우&mut T에서&mut U로.
-
TyCtor(
T)에서 TyCtor(U)로. 여기서 TyCtor(T)는 다음 중 하나입니다.-
&T -
&mut T -
*const T -
*mut T -
Box<T>여기서U는 크기 미지정 강제 변환(unsized coercion) 을 통해T로부터 얻을 수 있는 타입입니다.
-
- 함수 아이템 타입에서
fn포인터로
- 캡처하지 않는 클로저에서
fn포인터로
!에서 임의의T로
Unsized coercions
다음 강제 변환들은 타입을 크기 미지정 타입으로 변환하는 것과 관련이 있기 때문에 크기 미지정 강제 변환(unsized coercions) 이라고 불립니다. 위에서 설명한 것처럼 다른 강제 변환이 허용되지 않는 몇 가지 경우에도 허용되지만, 여전히 다른 강제 변환이 발생할 수 있는 모든 곳에서도 발생할 수 있습니다.
이 과정을 돕고 라이브러리에서 사용할 수 있도록 노출하기 위해 Unsize 와 CoerceUnsized 라는 두 트레잇이 사용됩니다. 다음 강제 변환들은 내장된 기능이며, 만약 T 가 이들 중 하나를 통해 U 로 강제 변환될 수 있다면, T 에 대한 Unsize<U> 구현이 제공됩니다.
[T; n]에서[T]로.
T가U + Sized를 구현하고U가 dyn 호환(dyn compatible) 인 경우,T에서dyn U로.
U가T의 상위 트레잇(supertraits) 중 하나인 경우,dyn T에서dyn U로.- 이는 자동 트레잇을 제거하는 것을 허용합니다. 즉,
dyn T + Auto에서dyn U로의 강제 변환이 허용됩니다. - 주요 트레잇(principal trait)이 자동 트레잇을 상위 트레잇으로 가지고 있는 경우 자동 트레잇을 추가하는 것을 허용합니다. 즉,
trait T: U + Send {}가 주어지면,dyn T에서dyn T + Send또는dyn U + Send로의 강제 변환이 허용됩니다.
- 이는 자동 트레잇을 제거하는 것을 허용합니다. 즉,
- 다음의 경우,
Foo<..., T, ...>에서Foo<..., U, ...>로.Foo는 구조체입니다.T가Unsize<U>를 구현합니다.Foo의 마지막 필드가T를 포함하는 타입을 가집니다.- 해당 필드의 타입이
Bar<T>라면,Bar<T>는Unsize<Bar<U>>를 구현합니다. T가 다른 필드의 타입에는 포함되지 않습니다.
추가적으로, T 가 Unsize<U> 또는 CoerceUnsized<Foo<U>> 를 구현하는 경우 타입 Foo<T> 는 CoerceUnsized<Foo<U>> 를 구현할 수 있습니다. 이를 통해 Foo<U> 로의 크기 미지정 강제 변환을 제공할 수 있습니다.
Note
While the definition of the unsized coercions and their implementation has been stabilized, the traits themselves are not yet stable and therefore can’t be used directly in stable Rust.
최소 상한 강제 변환
일부 문맥에서 컴파일러는 가장 일반적인 타입을 찾기 위해 여러 타입을 함께 강제 변환해야 합니다. 이를 “최소 상한(Least Upper Bound)” 강제 변환이라고 합니다. LUB 강제 변환은 오직 다음과 같은 상황에서만 사용됩니다.
- 일련의 if 분기들에 대한 공통 타입을 찾을 때.
- 일련의 매치 암(match arms)들에 대한 공통 타입을 찾을 때.
- 배열 요소들에 대한 공통 타입을 찾을 때.
- To find the common type for a labeled block expression among the break operands and the final block operand.
- To find the common type for an
loopexpression with break expressions among the break operands. - 여러 개의 return 문이 있는 클로저의 반환 타입을 찾을 때.
- 여러 개의 return 문이 있는 함수의 반환 타입을 검사할 때.
이러한 각 사례에서, 시작 시점에는 알 수 없는 어떤 대상 타입 T_t 로 서로 강제 변환되어야 하는 일련의 타입 T0..Tn 이 존재합니다.
LUB 강제 변환 계산은 반복적으로 수행됩니다. 대상 타입 T_t 는 타입 T0 에서 시작합니다. 각각의 새로운 타입 Ti 에 대해 다음을 고려합니다.
Ti가 현재 대상 타입T_t로 강제 변환될 수 있다면, 아무런 변경도 하지 않습니다.
- 그렇지 않은 경우,
T_t가Ti로 강제 변환될 수 있는지 확인합니다. 만약 가능하다면,T_t는Ti로 변경됩니다. (이 확인 과정은 지금까지 고려된 모든 소스 표현식들에 암시적 강제 변환이 있는지 여부도 조건으로 합니다.)
- 그렇지 않다면,
T_t와Ti사이의 상호 상위 타입(mutual supertype) 계산을 시도하며, 이것이 새로운 대상 타입이 됩니다.
예:
#![allow(unused)]
fn main() {
let (a, b, c) = (0, 1, 2);
// if 분기들의 경우
let bar = if true {
a
} else if false {
b
} else {
c
};
// 매치 암들의 경우
let baw = match 42 {
0 => a,
1 => b,
_ => c,
};
// 배열 요소들의 경우
let bax = [a, b, c];
// 여러 개의 return 문이 있는 클로저의 경우
let clo = || {
if true {
a
} else if false {
b
} else {
c
}
};
let baz = clo();
// 여러 개의 return 문이 있는 함수의 타입 검사의 경우
fn foo() -> i32 {
let (a, b, c) = (0, 1, 2);
match 42 {
0 => a,
1 => b,
_ => c,
}
}
}
이 예제들에서 ba* 변수들의 타입은 LUB 강제 변환을 통해 찾아집니다. 그리고 컴파일러는 함수 foo 를 처리하는 과정에서 a, b, c 의 LUB 강제 변환 결과가 i32 인지 확인합니다.
주의 사항
이 설명은 분명히 비공식적인 것입니다. 이를 더 정밀하게 만드는 작업은 러스트 타입 검사기를 더 정밀하게 명세하려는 전반적인 노력의 일환으로 진행될 예정입니다.
Divergence
A diverging expression is an expression that never completes normal execution.
#![allow(unused)]
fn main() {
fn diverges() -> ! {
panic!("This function never returns!");
}
fn example() {
let x: i32 = diverges(); // This line never completes.
println!("This is never printed: {x}");
}
}
See the following rules for specific expression divergence behavior:
- expr.block.diverging — Block expressions.
- expr.if.diverging —
ifexpressions. - expr.loop.block-labels.type — Labeled block expressions with
break. - expr.loop.break-value.diverging —
loopexpressions withbreak. - expr.loop.break.diverging —
breakexpressions. - expr.loop.continue.diverging —
continueexpressions. - expr.loop.infinite.diverging — Infinite
loopexpressions. - expr.match.diverging —
matchexpressions. - expr.match.empty — Empty
matchexpressions. - expr.return.diverging —
returnexpressions. - type.never.constraint — Function calls returning
!.
Note
The
panic!macro and related panic-generating macros likeunreachable!also have the type!and are diverging.
Any expression of type ! is a diverging expression. However, diverging expressions are not limited to type !; expressions of other types may also diverge (e.g., Some(loop {}) has type Option<!>).
Note
Though
!is considered an uninhabited type, a type being uninhabited is not sufficient for it to diverge.#![allow(unused)] fn main() { enum Empty {} fn make_never() -> ! {loop{}} fn make_empty() -> Empty {loop{}} fn diverging() -> ! { // This has a type of `!`. // So, the entire function is considered diverging. make_never(); // OK: The type of the body is `!` which matches the return type. } fn not_diverging() -> ! { // This type is uninhabited. // However, the entire function is not considered diverging. make_empty(); // ERROR: The type of the body is `()` but expected type `!`. } }
Note
Divergence can propagate to the surrounding block. See expr.block.diverging.
Fallback
If a type to be inferred is only unified with diverging expressions, then that type will be inferred to be !.
Example
#![allow(unused)] fn main() { fn foo() -> i32 { 22 } match foo() { // ERROR: The trait bound `!: Default` is not satisfied. 4 => Default::default(), _ => return, }; }
2024 Edition differences
Before the 2024 edition, the type was inferred to instead be
().
Note
Importantly, type unification may happen structurally, so the fallback
!may be part of a larger type. The following compiles:#![allow(unused)] fn main() { fn foo() -> i32 { 22 } // This has the type `Option<!>`, not `!` match foo() { 4 => Default::default(), _ => Some(return), }; }
소멸자
When an initialized variable or temporary goes out of scope, its destructor is run or it is dropped. Assignment also runs the destructor of its left-hand operand, if it’s initialized. If a variable has been partially initialized, only its initialized fields are dropped.
타입 T 의 소멸자는 다음과 같이 구성됩니다.
- If
T: Drop, calling<T as core::ops::Drop>::drop - 모든 필드에 대해 재귀적으로 소멸자 실행.
- 구조체(struct) 의 필드들은 선언된 순서대로 드롭됩니다.
- 활성 열거형 변형(enum variant) 의 필드들은 선언된 순서대로 드롭됩니다.
- 튜플(tuple) 의 필드들은 순서대로 드롭됩니다.
- 배열(array) 또는 소유권이 있는 슬라이스(slice) 의 요소들은 첫 번째 요소부터 마지막 요소 순으로 드롭됩니다.
- 클로저(closure) 가 이동(move)으로 캡처한 변수들은 지정되지 않은 순서대로 드롭됩니다.
- 트레잇 객체(Trait objects) 는 기저 타입(underlying type)의 소멸자를 실행합니다.
- 다른 타입들은 추가적인 드롭을 발생시키지 않습니다.
If a destructor must be run manually, such as when implementing your own smart pointer, core::ptr::drop_in_place can be used.
몇 가지 예제:
#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
fn drop(&mut self) {
println!("{}", self.0);
}
}
let mut overwritten = PrintOnDrop("덮어써질 때 드롭됨");
overwritten = PrintOnDrop("스코프가 끝날 때 드롭됨");
let tuple = (PrintOnDrop("튜플 첫 번째"), PrintOnDrop("튜플 두 번째"));
let moved;
// 할당 시 소멸자가 실행되지 않음.
moved = PrintOnDrop("이동될 때 드롭됨");
// 지금 드롭되지만, 그 후에는 초기화되지 않은 상태가 됩니다.
moved;
// 초기화되지 않은 상태에서는 드롭되지 않습니다.
let uninitialized: PrintOnDrop;
// 부분 이동 후에는 남아 있는 필드만 드롭됩니다.
let mut partial_move = (PrintOnDrop("first"), PrintOnDrop("잊힘(forgotten)"));
// 부분 이동을 수행하여 `partial_move.0` 만 초기화된 상태로 남깁니다.
core::mem::forget(partial_move.1);
// partial_move의 스코프가 끝나면 첫 번째 필드만 드롭됩니다.
}
드롭 스코프
각 변수나 임시 값은 드롭 스코프(drop scope) 와 연관됩니다. 제어 흐름이 드롭 스코프를 벗어나면 해당 스코프와 연관된 모든 변수는 선언된 역순으로, 임시 값은 생성된 역순으로 드롭됩니다.
Drop scopes can be determined by replacing for, if, and while expressions with equivalent expressions using match, loop and break.
오버로드된 연산자는 내장 연산자와 구별되지 않으며, 바인딩 모드(binding modes) 는 고려되지 않습니다.
함수 또는 클로저가 주어지면, 다음과 같은 것들에 대한 드롭 스코프가 존재합니다.
- 함수 전체
- 함수 본문을 포함한 각 블록
- 블록 표현식(block expression) 의 경우, 블록의 스코프와 표현식의 스코프는 동일합니다.
match표현식의 각 암(arm)
드롭 스코프들은 다음과 같이 서로 중첩됩니다. 함수에서 반환될 때와 같이 여러 스코프를 한꺼번에 벗어날 때, 변수들은 안쪽에서 바깥쪽 순서로 드롭됩니다.
- 함수 전체 스코프가 최외곽 스코프입니다.
- 함수 본문 블록은 함수 전체 스코프 내에 포함됩니다.
- 표현식 구문 내 표현식의 부모는 해당 구문의 스코프입니다.
let구문(let statement) 의 초기화식의 부모는 해당let구문의 스코프입니다.
- 구문 스코프의 부모는 해당 구문을 포함하는 블록의 스코프입니다.
match가드(guard) 표현식의 부모는 해당 가드가 속한 매치 암의 스코프입니다.
match표현식에서=>뒤에 오는 표현식의 부모는 그것이 속한 매치 암의 스코프입니다.
- 매치 암 스코프의 부모는 그것이 속한
match표현식의 스코프입니다.
- 그 외 모든 스코프의 부모는 그것을 직접 감싸고 있는 표현식의 스코프입니다.
함수 매개변수의 스코프
모든 함수 매개변수는 함수 본문 전체의 스코프에 속하므로, 함수가 평가될 때 가장 마지막에 드롭됩니다. 각 실제 함수 매개변수는 해당 매개변수의 패턴에서 도입된 모든 바인딩이 드롭된 후에 드롭됩니다.
#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
fn drop(&mut self) {
println!("드롭({})", self.0);
}
}
// `y` 가 드롭되고, 그다음 두 번째 매개변수, 그다음 `x`, 그다음 첫 번째 매개변수 순으로 드롭됩니다.
fn patterns_in_parameters(
(x, _): (PrintOnDrop, PrintOnDrop),
(_, y): (PrintOnDrop, PrintOnDrop),
) {}
// 드롭 순서는 3 2 0 1입니다.
patterns_in_parameters(
(PrintOnDrop("0"), PrintOnDrop("1")),
(PrintOnDrop("2"), PrintOnDrop("3")),
);
}
지역 변수의 스코프
Local variables declared in a let statement are associated to the scope of the block that contains the let statement.
#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
fn drop(&mut self) {
println!("드롭({})", self.0);
}
}
let declared_first = PrintOnDrop("바깥쪽 스코프에서 마지막에 드롭됨");
{
let declared_in_block = PrintOnDrop("안쪽 스코프에서 드롭됨");
}
let declared_last = PrintOnDrop("바깥쪽 스코프에서 먼저 드롭됨");
}
Local variables declared in a match expression or pattern-matching match guard are associated to the arm scope of the match arm that they are declared in.
#![allow(unused)]
fn main() {
#![allow(irrefutable_let_patterns)]
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
fn drop(&mut self) {
println!("드롭({})", self.0);
}
}
match PrintOnDrop("Dropped last in the first arm's scope") {
// When guard evaluation succeeds, control-flow stays in the arm and
// values may be moved from the scrutinee into the arm's bindings,
// causing them to be dropped in the arm's scope.
x if let y = PrintOnDrop("Dropped second in the first arm's scope")
&& let z = PrintOnDrop("Dropped first in the first arm's scope") =>
{
let declared_in_block = PrintOnDrop("안쪽 스코프에서 드롭됨");
// Pattern-matching guards' bindings and temporaries are dropped in
// reverse order, dropping each guard condition operand's bindings
// before its temporaries. Lastly, variables bound by the arm's
// pattern are dropped.
}
_ => unreachable!(),
}
match PrintOnDrop("Dropped in the enclosing temporary scope") {
// When guard evaluation fails, control-flow leaves the arm scope,
// causing bindings and temporaries from earlier pattern-matching
// guard condition operands to be dropped. This occurs before evaluating
// the next arm's guard or body.
_ if let y = PrintOnDrop("Dropped in the first arm's scope")
&& false => unreachable!(),
// When a guard is executed multiple times due to self-overlapping
// or-patterns, control-flow leaves the arm scope when the guard fails
// and re-enters the arm scope before executing the guard again.
_ | _ if let y = PrintOnDrop("Dropped in the second arm's scope twice")
&& false => unreachable!(),
_ => {},
}
}
Variables in patterns are dropped in reverse order of declaration within the pattern.
#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
fn drop(&mut self) {
println!("드롭({})", self.0);
}
}
let (declared_first, declared_last) = (
PrintOnDrop("Dropped last"),
PrintOnDrop("Dropped first"),
);
}
For the purpose of drop order, or-patterns declare bindings in the order given by the first subpattern.
#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
fn drop(&mut self) {
println!("드롭({})", self.0);
}
}
// Drops `x` before `y`.
fn or_pattern_drop_order<T>(
(Ok([x, y]) | Err([y, x])): Result<[T; 2], [T; 2]>
// ^^^^^^^^^^ ^^^^^^^^^^^ This is the second subpattern.
// |
// This is the first subpattern.
//
// In the first subpattern, `x` is declared before `y`. Since it is
// the first subpattern, that is the order used even if the second
// subpattern, where the bindings are declared in the opposite
// order, is matched.
) {}
// Here we match the first subpattern, and the drops happen according
// to the declaration order in the first subpattern.
or_pattern_drop_order(Ok([
PrintOnDrop("Declared first, dropped last"),
PrintOnDrop("Declared last, dropped first"),
]));
// Here we match the second subpattern, and the drops still happen
// according to the declaration order in the first subpattern.
or_pattern_drop_order(Err([
PrintOnDrop("Declared last, dropped first"),
PrintOnDrop("Declared first, dropped last"),
]));
}
임시 스코프
어떤 표현식의 임시 스코프(temporary scope) 는 해당 표현식이 장소 컨텍스트(place context) 에서 사용될 때 그 결과를 보관하는 임시 변수에 사용되는 스코프입니다. 다만 해당 표현식이 승격(promoted) 된 경우는 제외합니다.
라이프타임 확장을 제외하면, 표현식의 임시 스코프는 해당 표현식을 포함하는 가장 작은 스코프이며 다음 중 하나입니다.
- 함수 전체.
- 구문(statement).
if,while또는loop표현식의 본문.if표현식의else블록.- The non-pattern matching condition expression of an
iforwhileexpression or a non-pattern-matchingmatchguard condition operand. - The pattern-matching guard, if present, and body expression for a
matcharm. - 지연 불리언 표현식(lazy boolean expression) 의 각 피연산자.
- The pattern-matching condition(s) and consequent body of
if(destructors.scope.temporary.edition2024). - The pattern-matching condition and loop body of
while. - 블록 꼬리 표현식(tail expression)의 전체 (destructors.scope.temporary.edition2024).
Note
match표현식의 스크루티니(scrutinee) 는 임시 스코프가 아니므로, 스크루티니 내의 임시 값들은match표현식 이후에 드롭될 수 있습니다. 예를 들어,match 1 { ref mut z => z };에서1을 위한 임시 값은 구문이 끝날 때까지 유지됩니다.
Note
The desugaring of a destructuring assignment restricts the temporary scope of its assigned value operand (the RHS). For details, see expr.assign.destructure.tmp-scopes.
2024 Edition differences
The 2024 edition added two new temporary scope narrowing rules:
if lettemporaries are dropped before theelseblock, and temporaries of tail expressions of blocks are dropped immediately after the tail expression is evaluated.
몇 가지 예제:
#![allow(unused)]
fn main() {
#![allow(irrefutable_let_patterns)]
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
fn drop(&mut self) {
println!("드롭({})", self.0);
}
}
let local_var = PrintOnDrop("지역 변수");
// 조건이 평가되면 드롭됨
if PrintOnDrop("If 조건").0 == "If 조건" {
// 블록 끝에서 드롭됨
PrintOnDrop("If 본문").0
} else {
unreachable!()
};
if let "if let 스크루티니" = PrintOnDrop("if let 스크루티니").0 {
PrintOnDrop("if let 결과절").0
// `if let 결과절` 이 여기서 드롭됨
}
// `if let 스크루티니` 가 여기서 드롭됨
else {
PrintOnDrop("if let else").0
// `if let else` 가 여기서 드롭됨
};
while let x = PrintOnDrop("while let scrutinee").0 {
PrintOnDrop("while let loop body").0;
break;
// `while let loop body` dropped here.
// `while let scrutinee` dropped here.
}
// 첫 번째 || 이전에 드롭됨
(PrintOnDrop("첫 번째 피연산자").0 == ""
// ) 이전에 드롭됨
|| PrintOnDrop("두 번째 피연산자").0 == "")
// ; 이전에 드롭됨
|| PrintOnDrop("세 번째 피연산자").0 == "";
// 스크루티니는 함수의 끝에서 지역 변수들 이전에 드롭됩니다
// (이것이 함수 본문 블록의 꼬리 표현식이기 때문입니다).
match PrintOnDrop("최종 표현식의 매치된 값") {
// Non-pattern-matching guards' temporaries are dropped once the
// condition has been evaluated
_ if PrintOnDrop("가드 조건").0 == "" => (),
// Pattern-matching guards' temporaries are dropped when leaving the
// arm's scope
_ if let "guard scrutinee" = PrintOnDrop("guard scrutinee").0 => {
let _ = &PrintOnDrop("lifetime-extended temporary in inner scope");
// `lifetime-extended temporary in inner scope` is dropped here
}
// `guard scrutinee` is dropped here
_ => (),
}
}
피연산자
Temporaries are also created to hold the result of operands to an expression while the other operands are evaluated. The temporaries are associated to the scope of the expression with that operand. Since the temporaries are moved from once the expression is evaluated, dropping them has no effect unless one of the operands to an expression breaks out of the expression, returns, or panics.
#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
fn drop(&mut self) {
println!("드롭({})", self.0);
}
}
loop {
// 튜플 표현식의 평가가 완료되지 않았으므로 피연산자들이 역순으로 드롭됩니다.
(
PrintOnDrop("바깥쪽 튜플 첫 번째"),
PrintOnDrop("바깥쪽 튜플 두 번째"),
(
PrintOnDrop("안쪽 튜플 첫 번째"),
PrintOnDrop("안쪽 튜플 두 번째"),
break,
),
PrintOnDrop("절대로 생성되지 않음"),
);
}
}
상수 승격
값 표현식을 'static 슬롯으로 승격시키는 것은, 해당 표현식을 상수로 작성하여 차용할 수 있고, 그 차용을 원래 표현식이 작성된 위치에서 런타임 동작의 변경 없이 역참조할 수 있을 때 발생합니다. 즉, 승격된 표현식은 컴파일 타임에 평가될 수 있어야 하며, 그 결과값은 내부 가변성(interior mutability) 이나 소멸자(destructors) 를 포함하지 않아야 합니다 (이러한 속성들은 가능한 경우 값을 기반으로 결정됩니다. 예를 들어, &None 은 허용되지 않는 것을 아무것도 포함하지 않으므로 항상 &'static Option<_> 타입을 가집니다).
임시 라이프타임 확장
Note
The exact rules for temporary lifetime extension are subject to change. This is describing the current behavior only.
let 구문의 표현식에 대한 임시 스코프는 때때로 let 구문을 포함하는 블록의 스코프로 확장(extended) 되기도 합니다. 이는 특정한 구문 규칙에 따라 일반적인 임시 스코프가 너무 작을 때 수행됩니다. 예를 들어:
#![allow(unused)]
fn main() {
let x = &mut 0;
// 보통이라면 임시 값은 지금쯤 드롭되었겠지만, `0` 을 위한 임시 값은 블록 끝까지 유지됩니다.
println!("{}", x);
}
라이프타임 확장은 static 및 const 아이템에도 적용되어, 임시 값이 프로그램이 종료될 때까지 유지되게 합니다. 예를 들어:
#![allow(unused)]
fn main() {
const C: &Vec<i32> = &Vec::new();
// 보통이라면 `Vec` 은 `C` 의 초기화식 내부에만 존재하므로 이는 댕글링 참조(dangling reference)가 되겠지만,
// 대신 차용(borrow)이 라이프타임 확장되어 사실상 `'static` 라이프타임을 갖게 됩니다.
println!("{:?}", C);
}
If a borrow, dereference, field, or tuple indexing expression has an extended temporary scope, then so does its operand. If an indexing expression has an extended temporary scope, then the indexed expression also has an extended temporary scope.
패턴에 기반한 연장
An extending pattern is either:
-
참조 또는 가변 참조로 바인딩하는 식별자 패턴(identifier pattern).
#![allow(unused)] fn main() { fn temp() {} let ref x = temp(); // Binds by reference. x; let ref mut x = temp(); // Binds by mutable reference. x; } -
A struct, tuple, tuple struct, slice, or or-pattern where at least one of the direct subpatterns is an extending pattern.
#![allow(unused)] fn main() { use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; static X: AtomicU64 = AtomicU64::new(0); struct W<T>(T); impl<T> Drop for W<T> { fn drop(&mut self) { X.fetch_add(1, Relaxed); } } let W { 0: ref x } = W(()); // Struct pattern. x; let W(ref x) = W(()); // Tuple struct pattern. x; let (W(ref x),) = (W(()),); // Tuple pattern. x; let [W(ref x), ..] = [W(())]; // Slice pattern. x; let (Ok(W(ref x)) | Err(&ref x)) = Ok(W(())); // Or pattern. x; // // All of the temporaries above are still live here. assert_eq!(0, X.load(Relaxed)); }
따라서 ref x, V(ref x), [ref x, y] 는 모두 연장 패턴이지만, x, &ref x, &(ref x,) 는 그렇지 않습니다.
let 구문의 패턴이 연장 패턴이면 초기화식 표현식의 임시 스코프가 연장됩니다.
#![allow(unused)]
fn main() {
fn temp() {}
// This is an extending pattern, so the temporary scope is extended.
let ref x = *&temp(); // OK
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// This is neither an extending pattern nor an extending expression,
// so the temporary is dropped at the semicolon.
let &ref x = *&&temp(); // 오류
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// This is not an extending pattern but it is an extending expression,
// so the temporary lives beyond the `let` statement.
let &ref x = &*&temp(); // OK
x;
}
표현식에 기반한 연장
초기화식이 있는 let 구문의 경우, 연장 표현식(extending expression) 은 다음 중 하나인 표현식입니다.
- 초기화식 표현식.
- The operand of an extending borrow expression.
- The super operands of an extending super macro call expression.
- 연장 배열(array), 캐스트(cast), 중괄호 구조체(braced struct), 또는 튜플(tuple) 표현식의 피연산자(들).
- The arguments to an extending tuple struct or tuple enum variant constructor expression.
- The final expression of an extending block expression except for an async block expression.
- The final expression of an extending
ifexpression’s consequent,else if, orelseblock. - An arm expression of an extending
matchexpression.
Note
The desugaring of a destructuring assignment makes its assigned value operand (the RHS) an extending expression within a newly-introduced block. For details, see expr.assign.destructure.tmp-ext.
So the borrow expressions in &mut 0, (&1, &mut 2), and Some(&mut 3) are all extending expressions. The borrows in &0 + &1 and f(&mut 0) are not.
The operand of an extending borrow expression has its temporary scope extended.
The super temporaries of an extending super macro call expression have their scopes extended.
Note
rustcdoes not treat array repeat operands of extending array expressions as extending expressions. Whether it should is an open question.For details, see Rust issue #146092.
예시
다음은 표현식이 연장된 임시 스코프를 갖는 몇 가지 예입니다.
#![allow(unused)]
fn main() {
use core::pin::pin;
use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
static X: AtomicU64 = AtomicU64::new(0);
#[derive(Debug)] struct S;
impl Drop for S { fn drop(&mut self) { X.fetch_add(1, Relaxed); } }
const fn temp() -> S { S }
let x = &temp(); // Operand of borrow.
x;
let x = &raw const *&temp(); // Operand of raw borrow.
assert_eq!(X.load(Relaxed), 0);
let x = &temp() as &dyn Send; // Operand of cast.
x;
let x = (&*&temp(),); // Operand of tuple constructor.
x;
struct W<T>(T);
let x = W(&temp()); // Argument to tuple struct constructor.
x;
let x = Some(&temp()); // Argument to tuple enum variant constructor.
x;
let x = { [Some(&temp())] }; // Final expr of block.
x;
let x = const { &temp() }; // Final expr of `const` block.
x;
let x = unsafe { &temp() }; // Final expr of `unsafe` block.
x;
let x = if true { &temp() } else { &temp() };
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Final exprs of `if`/`else` blocks.
x;
let x = match () { _ => &temp() }; // `match` arm expression.
x;
let x = pin!(temp()); // Super operand of super macro call expression.
x;
let x = pin!({ &mut temp() }); // As above.
x;
let x = format_args!("{:?}", temp()); // As above.
x;
//
// All of the temporaries above are still live here.
assert_eq!(0, X.load(Relaxed));
}
다음은 표현식이 연장된 임시 스코프를 갖지 않는 몇 가지 예입니다.
#![allow(unused)]
fn main() {
fn temp() {}
// Arguments to function calls are not extending expressions. The
// temporary is dropped at the semicolon.
let x = core::convert::identity(&temp()); // 오류
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
trait Use { fn use_temp(&self) -> &Self { self } }
impl Use for () {}
// Receivers of method calls are not extending expressions.
let x = (&temp()).use_temp(); // 오류
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// Scrutinees of match expressions are not extending expressions.
let x = match &temp() { x => x }; // 오류
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// Final expressions of `async` blocks are not extending expressions.
let x = async { &temp() }; // 오류
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// Final expressions of closures are not extending expressions.
let x = || &temp(); // 오류
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// Operands of loop breaks are not extending expressions.
let x = loop { break &temp() }; // 오류
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// Operands of breaks to labels are not extending expressions.
let x = 'a: { break 'a &temp() }; // 오류
x;
}
#![allow(unused)]
fn main() {
use core::pin::pin;
fn temp() {}
// The argument to `pin!` is only an extending expression if the call
// is an extending expression. Since it's not, the inner block is not
// an extending expression, so the temporaries in its trailing
// expression are dropped immediately.
pin!({ &temp() }); // 오류
}
#![allow(unused)]
fn main() {
fn temp() {}
// As above.
format_args!("{:?}", { &temp() }); // 오류
}
소멸자를 실행하지 않는 경우
Manually suppressing destructors
core::mem::forget can be used to prevent the destructor of a variable from being run, and core::mem::ManuallyDrop provides a wrapper to prevent a variable or field from being dropped automatically.
Note
Preventing a destructor from being run via
core::mem::forgetor other means is safe even if it has a type that isn’t'static. Besides the places where destructors are guaranteed to run as defined by this document, types may not safely rely on a destructor being run for soundness.
Process termination without unwinding
There are some ways to terminate the process without unwinding, in which case destructors will not be run.
The standard library provides std::process::exit and std::process::abort to do this explicitly. Additionally, if the panic handler is set to abort, panicking will always terminate the process without destructors being run.
There is one additional case to be aware of: when a panic reaches a non-unwinding ABI boundary, either no destructors will run, or all destructors up until the ABI boundary will run.
라이프타임 생략
러스트에는 컴파일러가 합리적인 기본 선택을 추론할 수 있는 다양한 위치에서 라이프타임을 생략할 수 있게 해주는 규칙들이 있습니다.
함수에서의 라이프타임 생략
일반적인 패턴을 더 인체공학적으로 만들기 위해, 함수 아이템(function item), 함수 포인터(function pointer), 클로저 트레잇(closure trait) 시그니처에서 라이프타임 인자가 생략(elided) 될 수 있습니다. 생략된 라이프타임에 대한 라이프타임 파라미터를 추론하기 위해 다음 규칙들이 사용됩니다.
추론할 수 없는 라이프타임 파라미터를 생략하는 것은 오류입니다.
플레이스홀더 라이프타임인 '_ 도 동일한 방식으로 라이프타임을 추론하도록 하기 위해 사용될 수 있습니다. 경로에서의 라이프타임에 대해서는 '_ 를 사용하는 것이 선호됩니다.
트레잇 객체 라이프타임은 아래 에서 논의될 다른 규칙을 따릅니다.
- 파라미터에서 생략된 각 라이프타임은 별개의 라이프타임 파라미터가 됩니다.
- 파라미터에서 사용된 라이프타임(생략되었든 아니든)이 정확히 하나라면, 그 라이프타임이 모든 생략된 출력 라이프타임에 할당됩니다.
메서드 시그니처에는 또 다른 규칙이 있습니다
- 리시버(receiver)가
&Self또는&mut Self타입을 가지면, 그Self에 대한 참조의 라이프타임이 모든 생략된 출력 라이프타임 파라미터에 할당됩니다.
예:
#![allow(unused)]
fn main() {
trait T {}
trait ToCStr {}
struct Thing<'a> {f: &'a i32}
struct Command;
trait Example {
fn print1(s: &str); // 생략됨
fn print2(s: &'_ str); // 역시 생략됨
fn print3<'a>(s: &'a str); // 확장됨
fn debug1(lvl: usize, s: &str); // 생략됨
fn debug2<'a>(lvl: usize, s: &'a str); // 확장됨
fn substr1(s: &str, until: usize) -> &str; // 생략됨
fn substr2<'a>(s: &'a str, until: usize) -> &'a str; // 확장됨
fn get_mut1(&mut self) -> &mut dyn T; // 생략됨
fn get_mut2<'a>(&'a mut self) -> &'a mut dyn T; // 확장됨
fn args1<T: ToCStr>(&mut self, args: &[T]) -> &mut Command; // 생략됨
fn args2<'a, 'b, T: ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // 확장됨
fn other_args1<'a>(arg: &str) -> &'a str; // 생략됨
fn other_args2<'a, 'b>(arg: &'b str) -> &'a str; // 확장됨
fn new1(buf: &mut [u8]) -> Thing<'_>; // 생략됨 - 선호됨
fn new2(buf: &mut [u8]) -> Thing; // 생략됨
fn new3<'a>(buf: &'a mut [u8]) -> Thing<'a>; // 확장됨
}
type FunPtr1 = fn(&str) -> &str; // 생략됨
type FunPtr2 = for<'a> fn(&'a str) -> &'a str; // 확장됨
type FunTrait1 = dyn Fn(&str) -> &str; // 생략됨
type FunTrait2 = dyn for<'a> Fn(&'a str) -> &'a str; // 확장됨
}
#![allow(unused)]
fn main() {
// 다음 예제들은 라이프타임 파라미터를 생략할 수 없는 상황들을 보여줍니다.
trait Example {
// 추론할 수 있는 파라미터가 없기 때문에 추론할 수 없습니다.
fn get_str() -> &str; // 허용되지 않음
// 첫 번째 파라미터에서 차용된 것인지 두 번째 파라미터에서 차용된 것인지 모호하기 때문에 추론할 수 없습니다.
fn frob(s: &str, t: &str) -> &str; // 허용되지 않음
}
}
기본 트레잇 객체 라이프타임
트레잇 객체(trait object) 가 보유한 참조의 추정 라이프타임을 기본 객체 라이프타임 바운드(default object lifetime bound) 라고 합니다. 이들은 RFC 599 에서 정의되었고 RFC 1156 에서 개정되었습니다.
이러한 기본 객체 라이프타임 바운드는 라이프타임 바운드가 완전히 생략되었을 때 위에서 정의된 라이프타임 파라미터 생략 규칙 대신 사용됩니다.
'_ 가 라이프타임 바운드로 사용되면 해당 바운드는 일반적인 생략 규칙을 따릅니다.
트레잇 객체가 제네릭 타입의 타입 인자로 사용되면, 먼저 이를 포함하는 타입을 사용하여 바운드를 추론하려고 시도합니다.
- If there is a unique bound from the containing type then that is the default.
- If there is more than one bound from the containing type then an explicit bound must be specified.
두 규칙 모두 적용되지 않는 경우, 트레잇에 지정된 바운드가 사용됩니다.
- 트레잇이 단일 라이프타임 바운드 와 함께 정의되었다면 그 바운드가 사용됩니다.
- 어떤 라이프타임 바운드에라도
'static이 사용되었다면'static이 사용됩니다.
- 트레잇에 라이프타임 바운드가 없다면, 라이프타임은 표현식 내에서는 추론되고 표현식 외부에서는
'static이 됩니다.
#![allow(unused)]
fn main() {
// 다음 트레잇에 대하여...
trait Foo { }
// Box<T>는 T에 대한 라이프타임 바운드가 없으므로 이 둘은 동일합니다.
type T1 = Box<dyn Foo>;
type T2 = Box<dyn Foo + 'static>;
// ...그리고 다음의 경우들도 그렇습니다:
impl dyn Foo {}
impl dyn Foo + 'static {}
// ...&'a T가 T: 'a를 요구하므로 다음의 경우들도 그렇습니다:
type T3<'a> = &'a dyn Foo;
type T4<'a> = &'a (dyn Foo + 'a);
// std::cell::Ref<'a, T> 역시 T: 'a를 요구하므로 이들은 동일합니다.
type T5<'a> = std::cell::Ref<'a, dyn Foo>;
type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>;
}
#![allow(unused)]
fn main() {
// 이는 오류의 한 예입니다.
trait Foo { }
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b> {
f1: &'a i32,
f2: &'b i32,
f3: T,
}
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo>;
// ^^^^^^^
// 오류: 이 객체 타입에 대한 라이프타임 바운드를 문맥으로부터 추론할 수 없습니다.
}
가장 안쪽의 객체가 바운드를 설정하므로, &'a Box<dyn Foo> 는 여전히 &'a Box<dyn Foo + 'static> 임에 유의하세요.
#![allow(unused)]
fn main() {
// 다음 트레잇에 대하여...
trait Bar<'a>: 'a { }
// ...이 둘은 동일합니다:
type T1<'a> = Box<dyn Bar<'a>>;
type T2<'a> = Box<dyn Bar<'a> + 'a>;
// ...그리고 다음의 경우들도 그렇습니다:
impl<'a> dyn Bar<'a> {}
impl<'a> dyn Bar<'a> + 'a {}
}
const 및 static 생략
상수(constant) 및 정적(static) 참조 타입 선언은 명시적인 라이프타임이 지정되지 않는 한 암시적인 'static 라이프타임을 갖습니다. 따라서 위에서 'static 을 포함하는 상수 선언들은 라이프타임 없이 작성될 수 있습니다.
#![allow(unused)]
fn main() {
// STRING: &'static str
const STRING: &str = "bitstring";
struct BitsNStrings<'a> {
mybits: [u32; 2],
mystring: &'a str,
}
// BITS_N_STRINGS: BitsNStrings<'static>
const BITS_N_STRINGS: BitsNStrings<'_> = BitsNStrings {
mybits: [1, 2],
mystring: STRING,
};
}
static 또는 const 아이템이 함수나 클로저 참조를 포함하고, 그 함수나 클로저가 다시 참조를 포함하는 경우, 컴파일러는 먼저 표준 생략 규칙을 시도합니다. 일반적인 규칙으로 라이프타임을 해결할 수 없다면 오류가 발생합니다. 예를 들어:
#![allow(unused)]
fn main() {
struct Foo;
struct Bar;
struct Baz;
fn somefunc(a: &Foo, b: &Bar, c: &Baz) -> usize {42}
// `for<'a> fn(&'a str) -> &'a str` 로 해결됩니다.
const RESOLVED_SINGLE: fn(&str) -> &str = |x| x;
// `for<'a, 'b, 'c> Fn(&'a Foo, &'b Bar, &'c Baz) -> usize` 로 해결됩니다.
const RESOLVED_MULTIPLE: &dyn Fn(&Foo, &Bar, &Baz) -> usize = &somefunc;
}
#![allow(unused)]
fn main() {
struct Foo;
struct Bar;
struct Baz;
fn somefunc<'a,'b>(a: &'a Foo, b: &'b Bar) -> &'a Baz {unimplemented!()}
// 인자 라이프타임과 관련하여 반환 참조 라이프타임을 제한할 정보가 충분하지 않으므로, 이는 오류입니다.
const RESOLVED_STATIC: &dyn Fn(&Foo, &Bar) -> &Baz = &somefunc;
// ^
// 이 함수의 반환 타입은 차용된 값을 포함하지만, 시그니처에는 이것이 인자 1에서 차용된 것인지 인자 2에서 차용된 것인지 명시되어 있지 않습니다.
}
특수 타입과 트레잇
표준 라이브러리 에 존재하는 특정 타입과 트레잇은 러스트 컴파일러에게 알려져 있습니다. 이 장에서는 이러한 타입과 트레잇의 특별한 기능들을 문서화합니다.
Box<T>
Box<T> 는 현재 러스트가 사용자 정의 타입에 대해서는 허용하지 않는 몇 가지 특별한 기능을 가지고 있습니다.
Box<T>에 대한 역참조 연산자(dereference operator) 는 데이터를 이동(move)할 수 있는 장소(place)를 생성합니다. 이는*연산자와Box<T>의 소멸자가 언어 자체에 내장되어 있음을 의미합니다.
- 메서드(Methods) 는
Box<Self>를 리시버로 취할 수 있습니다.
- 고아 규칙(orphan rules) 이 다른 제네릭 타입에 대해서는 금지하는 것과 달리,
T와 동일한 크레이트에서Box<T>에 대해 트레잇을 구현할 수 있습니다.
Rc<T>
메서드(Methods) 는 Rc<Self> 를 리시버로 취할 수 있습니다.
Arc<T>
메서드(Methods) 는 Arc<Self> 를 리시버로 취할 수 있습니다.
Pin<P>
메서드(Methods) 는 Pin<P> 를 리시버로 취할 수 있습니다.
UnsafeCell<T>
std::cell::UnsafeCell<T> 는 내부 가변성(interior mutability) 을 위해 사용됩니다. 이는 컴파일러가 해당 타입들에 대해 부적절한 최적화를 수행하지 않도록 보장합니다.
또한 내부 가변성을 가진 타입을 가진 static 아이템 이 읽기 전용으로 표시된 메모리에 배치되지 않도록 보장합니다.
PhantomData<T>
std::marker::PhantomData<T> 는 크기가 0이고 정렬(alignment)이 최소인 타입으로, 가변성(variance), 드롭 검사(drop check), 그리고 자동 트레잇(auto traits) 의 목적을 위해 T 를 소유하는 것으로 간주됩니다.
Operator traits
std::ops 및 std::cmp 에 있는 트레잇들은 연산자(operators), 인덱싱 표현식(indexing expressions), 그리고 호출 표현식(call expressions) 을 오버로드하는 데 사용됩니다.
Deref 및 DerefMut
단항 * 연산자를 오버로드하는 것뿐만 아니라, Deref 와 DerefMut 는 메서드 확인(method resolution) 및 deref 강제 변환(deref coercions) 에서도 사용됩니다.
Drop
Drop 트레잇은 이 타입의 값이 파괴될 때마다 실행될 소멸자(destructor) 를 제공합니다.
Copy
Copy 트레잇은 이를 구현하는 타입의 의미론(semantics)을 변경합니다.
Copy 를 구현하는 타입의 값은 할당 시 이동(move)되지 않고 복사(copy)됩니다.
Copy 는 Drop 을 구현하지 않고 모든 필드가 Copy 인 타입에 대해서만 구현될 수 있습니다. 열거형의 경우 모든 변형(variant)의 모든 필드가 Copy 여야 함을 의미합니다. 공용체(union)의 경우 모든 변형이 Copy 여야 함을 의미합니다.
컴파일러는 다음의 경우들에 대해 Copy 를 구현합니다.
Copy타입들로 구성된 튜플(Tuples)
- 아무 값도 캡처하지 않거나
Copy타입의 값만 캡처하는 클로저(Closures)
Clone
Clone 트레잇은 Copy 의 상위 트레잇(supertrait)이므로, 역시 컴파일러가 생성한 구현이 필요합니다.
컴파일러는 다음 타입들에 대해 이를 구현합니다.
- 내장된
Copy구현을 가진 타입 (위 참조)
Clone타입들로 구성된 튜플(Tuples)
Clone타입의 값만 캡처하거나 환경에서 아무 값도 캡처하지 않는 클로저(Closures)
Send
Send 트레잇은 이 타입의 값이 한 스레드에서 다른 스레드로 안전하게 전달될 수 있음을 나타냅니다.
Sync
Sync 트레잇은 이 타입의 값을 여러 스레드 간에 안전하게 공유할 수 있음을 나타냅니다.
이 트레잇은 불변 static 아이템(static items) 에 사용되는 모든 타입에 대해 구현되어야 합니다.
Termination
Termination 트레잇은 메인 함수(main function) 및 테스트 함수(test functions) 에서 허용되는 반환 타입들을 나타냅니다.
자동 트레잇(Auto traits)
Send, Sync, Unpin, UnwindSafe, 그리고 RefUnwindSafe 트레잇은 자동 트레잇(auto traits) 입니다. 자동 트레잇은 특별한 속성을 가집니다.
주어진 타입에 대해 자동 트레잇에 대한 명시적인 구현이나 부정적인 구현(negative implementation)이 작성되어 있지 않다면, 컴파일러는 다음 규칙에 따라 자동으로 이를 구현합니다.
T가 해당 트레잇을 구현하면&T,&mut T,*const T,*mut T,[T; n], 그리고[T]도 이를 구현합니다.
- 함수 아이템 타입과 함수 포인터는 자동으로 트레잇을 구현합니다.
- 구조체, 열거형, 공용체, 그리고 튜플은 모든 필드가 해당 트레잇을 구현하면 이를 구현합니다.
- 클로저는 캡처한 모든 것의 타입이 해당 트레잇을 구현하면 이를 구현합니다.
T를 공유 참조로 캡처하고U를 값으로 캡처하는 클로저는&T와U가 모두 구현하는 자동 트레잇들을 구현합니다.
제네릭 타입의 경우 (위의 내장 타입들도 T 에 대한 제네릭으로 간주함), 제네릭 구현이 가능하다면 컴파일러는 필요한 트레잇 바운드를 충족하지 못해 해당 구현을 사용할 수 없는 타입들에 대해 자동으로 이를 구현하지 않습니다. 예를 들어, 표준 라이브러리는 T 가 Sync 인 모든 &T 에 대해 Send 를 구현합니다. 이는 T 가 Send 이지만 Sync 가 아닌 경우 컴파일러가 &T 에 대해 Send 를 구현하지 않음을 의미합니다.
자동 트레잇은 또한 부정적인 구현을 가질 수 있으며, 이는 표준 라이브러리 문서에서 impl !AutoTrait for T 로 표시되어 자동 구현을 재정의(override)합니다. 예를 들어 *mut T 는 Send 에 대한 부정적인 구현을 가지고 있으므로, T 가 Send 이더라도 *mut T 는 Send 가 아닙니다. 현재 추가적인 부정적인 구현을 지정하는 안정적인 방법은 없으며, 표준 라이브러리에만 존재합니다.
자동 트레잇은 보통 하나의 트레잇만 허용되는 트레잇 객체(trait object) 에도 추가적인 바운드로 추가될 수 있습니다. 예를 들어, Box<dyn Debug + Send + UnwindSafe> 는 유효한 타입입니다.
Sized
Sized 트레잇은 이 타입의 크기가 컴파일 타임에 알려져 있음을 나타냅니다. 즉, 동적 크기 타입(dynamically sized type) 이 아님을 의미합니다.
타입 파라미터(Type parameters) (트레잇의 Self 제외)와 연관 타입(associated types) 은 기본적으로 Sized 입니다.
Sized 는 항상 구현 아이템(implementation items) 이 아니라 컴파일러에 의해 자동으로 구현됩니다.
이러한 암시적인 Sized 바운드는 특별한 ?Sized 바운드를 사용하여 완화될 수 있습니다.
이름
엔티티(entity) 는 소스 프로그램 내에서 어떤 방식으로든(보통 경로(path) 를 통해) 참조될 수 있는 언어 구성 요소입니다. 엔티티에는 타입, 아이템, 제네릭 파라미터, 변수 바인딩, 루프 레이블, 라이프타임, 필드, 속성(attributes), 그리고 린트(lints) 가 포함됩니다.
선언(declaration) 은 엔티티를 참조하기 위한 _이름 _을 도입할 수 있는 구문론적 구성 요소입니다. 엔티티 이름은 _ 스코프(scope)_(해당 이름이 참조될 수 있는 소스 텍스트의 영역) 내에서 유효합니다.
일부 엔티티는 소스 코드에서 명시적으로 선언 되며, 일부는 언어의 일부나 컴파일러 확장의 일환으로 암시적으로 선언 됩니다.
경로(Paths) 는 엔티티(다른 모듈이나 타입에 있을 수 있음)를 참조하는 데 사용됩니다.
라이프타임과 루프 레이블은 앞에 따옴표가 붙는 전용 구문 을 사용합니다.
이름은 서로 다른 네임스페이스(namespaces) 로 격리되어, 서로 다른 네임스페이스에 있는 엔티티들이 충돌 없이 동일한 이름을 공유할 수 있게 합니다.
이름 확인(Name resolution) 은 경로, 식별자, 레이블을 엔티티 선언에 연결하는 컴파일 타임 프로세스입니다.
특정 이름에 대한 접근은 해당 이름의 가시성(visibility) 에 따라 제한될 수 있습니다.
명시적으로 선언된 엔티티
소스 코드에서 명시적으로 이름을 도입하는 엔티티는 다음과 같습니다.
let문 패턴 바인딩
macro_use속성](macros-by-example.md#the-macro_use-attribute)은 다른 크레이트에서 매크로 이름을 가져올 수 있습니다.
- The
macro_exportattribute can introduce an alias for the macro into the crate root
또한, 매크로 호출 및 속성 은 위의 항목 중 하나로 확장되어 이름을 도입할 수 있습니다.
암시적으로 선언된 엔티티
다음 엔티티들은 언어에 의해 암시적으로 정의되거나, 컴파일러 옵션 및 확장 기능을 통해 도입됩니다.
- 표준 라이브러리 프렐류드 아이템, 속성 및 매크로
- 루트 모듈의 표준 라이브러리 크레이트
- 컴파일러에 의해 링크된 외부 크레이트
- Derive 헬퍼 속성 은 명시적으로 가져오지 않아도 아이템 내에서 유효합니다.
'static라이프타임
또한, 크레이트 루트 모듈은 이름을 가지지 않지만, 특정 경로 한정자 나 별칭을 통해 참조될 수 있습니다.
네임스페이스
네임스페이스 는 선언된 이름 의 논리적 그룹입니다. 이름은 해당 이름이 참조하는 엔티티의 종류에 따라 별도의 네임스페이스로 분리됩니다. 네임스페이스를 사용하면 한 네임스페이스에 있는 이름이 다른 네임스페이스에 있는 같은 이름과 충돌하지 않습니다.
각각 다른 종류의 엔티티를 포함하는 여러 가지 네임스페이스가 있습니다. 이름의 사용은 이름 해석 장에서 설명한 대로 문맥에 따라 다른 네임스페이스에서 해당 이름의 선언을 찾습니다.
다음은 네임스페이스와 그에 해당하는 엔티티의 목록입니다:
- 타입 네임스페이스
- 모듈 선언
- 외부 크레이트 선언
- 외부 크레이트 프렐류드 아이템
- 구조체, 공용체, 열거형, 열거형 변형 선언
- 트레잇 아이템 선언
- 타입 별칭
- 연관 타입 선언
- Built-in types: boolean, numeric,
char, andstr - 제네릭 타입 매개변수
Self타입- 도구 속성 모듈
- 값 네임스페이스
- 함수 선언
- 상수 아이템 선언
- 정적 아이템 선언
- 구조체 생성자
- 열거형 변형 생성자
Self생성자- 제네릭 상수 매개변수
- 연관 상수 선언
- 연관 함수 선언
- Local bindings —
let,if let,while let,for,matcharms, function parameters, closure parameters - 캡처된 클로저 변수
- 매크로 네임스페이스
- 라이프타임 네임스페이스
- 레이블 네임스페이스
다른 네임스페이스에서 겹치는 이름을 모호하지 않게 사용하는 예:
#![allow(unused)]
fn main() {
// Foo는 타입 네임스페이스에 타입을 도입하고 값 네임스페이스에
// 생성자를 도입합니다.
struct Foo(u32);
// `Foo` 매크로는 매크로 네임스페이스에 선언됩니다.
macro_rules! Foo {
() => {};
}
// `f` 매개변수 타입의 `Foo` 는 타입 네임스페이스의 `Foo` 를 참조합니다.
// `'Foo` 는 라이프타임 네임스페이스에 새로운 라이프타임을 도입합니다.
fn example<'Foo>(f: Foo) {
// `Foo` 는 값 네임스페이스의 `Foo` 생성자를 참조합니다.
let ctor = Foo;
// `Foo` 는 매크로 네임스페이스의 `Foo` 매크로를 참조합니다.
Foo!{}
// `'Foo` 는 레이블 네임스페이스에 레이블을 도입합니다.
'Foo: loop {
// `'Foo` 는 `'Foo` 라이프타임 매개변수를 참조하며, `Foo` 는
// 타입 네임스페이스를 참조합니다.
let x: &'Foo Foo;
// `'Foo` 는 레이블을 참조합니다.
break 'Foo;
}
}
}
네임스페이스가 없는 명명된 엔터티
다음 엔터티는 명시적인 이름을 가지고 있지만, 그 이름은 특정 네임스페이스의 일부가 아닙니다.
필드
구조체, 열거형 및 유니온 필드에 이름이 지정되어 있더라도, 명명된 필드는 명시적 네임스페이스에 존재하지 않습니다. 이들은 필드 표현식 을 통해서만 접근할 수 있으며, 이는 접근하려는 특정 타입의 필드 이름만 검사합니다.
Use 선언
Use 선언 은 스코프로 가져오는 명명된 별칭을 가지지만, use 아이템 자체는 특정 네임스페이스에 속하지 않습니다. 대신, 가져오는 아이템 종류에 따라 여러 네임스페이스에 별칭을 도입할 수 있습니다.
서브 네임스페이스
매크로 네임스페이스는 두 개의 서브 네임스페이스로 나뉩니다: 하나는 느낌표 스타일 매크로 용이고 다른 하나는 속성 용입니다. 속성이 확인될 때, 스코프 내의 느낌표 스타일 매크로는 무시됩니다. 반대로 느낌표 스타일 매크로를 확인할 때는 스코프 내의 속성 매크로가 무시됩니다. 이는 한 스타일이 다른 스타일을 가리는 것을 방지합니다.
예를 들어, cfg 속성 과 cfg 매크로 는 매크로 네임스페이스에서 같은 이름을 가진 서로 다른 두 엔터티이지만, 여전히 각각의 컨텍스트에서 사용할 수 있습니다.
Note
useimports still cannot create duplicate bindings of the same name in a module or block, regardless of sub-namespace.#[macro_export] macro_rules! mymac { () => {}; } use myattr::mymac; // error[E0252]: the name `mymac` is defined multiple times.
스코프
스코프 는 명명된 엔터티 가 해당 이름으로 참조될 수 있는 소스 텍스트 영역입니다. 다음 섹션에서는 엔터티의 종류와 선언된 위치에 따라 달라지는 스코핑 규칙 및 동작에 대한 세부 정보를 제공합니다. 이름이 엔터티로 확인되는 과정은 이름 확인 장에 설명되어 있습니다. 소멸자를 실행하기 위해 사용되는 “드롭 스코프“에 대한 자세한 정보는 소멸자 장에서 찾을 수 있습니다.
아이템 스코프
모듈 에 직접 선언된 아이템 의 이름은 모듈의 시작부터 모듈의 끝까지 확장되는 스코프를 가집니다. 이 아이템들은 또한 모듈의 멤버이며 해당 모듈에서 이어지는 경로 로 참조할 수 있습니다.
구문 으로 선언된 아이템의 이름은 아이템 구문이 있는 블록의 시작부터 블록의 끝까지 확장되는 스코프를 가집니다.
동일한 모듈이나 블록 내의 동일한 네임스페이스 에 있는 다른 아이템과 중복된 이름으로 아이템을 도입하는 것은 오류입니다. 별표 글롭 임포트 는 중복된 이름과 가림을 처리하는 데 특별한 동작을 가지고 있으며, 자세한 내용은 링크된 장을 참조하십시오.
모듈의 아이템은 프렐류드 의 아이템을 가릴 수 있습니다.
외부 모듈의 아이템 이름은 중첩된 모듈 내의 스코프에 없습니다. 경로 를 사용하여 다른 모듈의 아이템을 참조할 수 있습니다.
연관 아이템 스코프
연관 아이템 은 스코프가 지정되지 않으며 연관된 타입이나 트레잇에서 이어지는 경로 를 사용해야만 참조할 수 있습니다. 메서드 는 호출 표현식 을 통해서도 참조할 수 있습니다.
모듈이나 블록 내의 아이템과 마찬가지로, 동일한 네임스페이스의 트레잇이나 구현 내에 다른 아이템과 중복되는 아이템을 트레잇이나 구현 내에 도입하는 것은 오류입니다.
패턴 바인딩 스코프
지역 변수 패턴 바인딩의 스코프는 사용되는 위치에 따라 다릅니다:
let구문 바인딩은let구문 바로 뒤부터 선언된 블록의 끝까지 범위가 지정됩니다.
- 함수 매개변수 바인딩은 함수 본문 내에 있습니다.
- 클로저 매개변수 바인딩은 클로저 본문 내에 있습니다.
forbindings are within the loop body.
if letandwhile letbindings are valid in the following conditions as well as the consequent block.
matchguardletbindings are valid in the following guard conditions and the match arm expression.
지역 변수 스코프는 아이템 선언으로 확장되지 않습니다.
패턴 바인딩 가림
패턴 바인딩은 오류인 다음 예외를 제외하고 스코프 내의 모든 이름을 가릴 수 있습니다:
- 상수 제네릭 매개변수
- 정적 아이템
- 상수 아이템
- 구조체 및 열거형 의 생성자
다음 예제는 지역 바인딩이 아이템 선언을 어떻게 가릴 수 있는지 보여줍니다:
#![allow(unused)]
fn main() {
fn shadow_example() {
// 아직 스코프에 지역 변수가 없으므로, 이것은 함수로 확인됩니다.
foo(); // `function` 출력
let foo = || println!("클로저");
fn foo() { println!("함수"); }
// 아이템을 가리므로 이것은 지역 클로저로 확인됩니다.
foo(); // `closure` 출력
}
}
제네릭 매개변수 스코프
Generic parameters are declared in a GenericParams list. The scope of a generic parameter is within the item it is declared on.
모든 매개변수는 선언된 순서와 관계없이 제네릭 매개변수 목록 내에서 스코프에 있습니다. 다음은 매개변수가 선언되기 전에 참조될 수 있는 몇 가지 예시를 보여줍니다:
#![allow(unused)]
fn main() {
// 'b 바운드는 선언되기 전에 참조됩니다.
fn params_scope<'a: 'b, 'b>() {}
trait SomeTrait<const Z: usize> {}
// 상수 N은 선언되기 전에 트레잇 바운드에서 참조됩니다.
fn f<T: SomeTrait<N>, const N: usize>() {}
}
제네릭 매개변수는 타입 바운드와 where 절에서도 스코프에 있습니다. 예를 들어:
#![allow(unused)]
fn main() {
trait SomeTrait<'a, T> {}
// `SomeTrait` 의 <'a, U>는 `bounds_scope` 의 'a와 U 매개변수를 참조합니다.
fn bounds_scope<'a, T: SomeTrait<'a, U>, U>() {}
fn where_scope<'a, T, U>()
where T: SomeTrait<'a, U>
{}
}
함수 내부에 선언된 아이템 이 외부 스코프의 제네릭 매개변수를 참조하는 것은 오류입니다.
#![allow(unused)]
fn main() {
fn example<T>() {
fn inner(x: T) {} // 오류: 외부 함수의 제네릭 매개변수를 사용할 수 없습니다
}
}
제네릭 매개변수 가리기(Shadowing)
함수 내에 선언된 아이템이 함수의 제네릭 매개변수 이름을 가리는 것이 허용되는 경우를 제외하고, 제네릭 매개변수를 가리는 것은 오류입니다.
#![allow(unused)]
fn main() {
fn example<'a, T, const N: usize>() {
// 함수 내의 아이템은 스코프 내의 제네릭 매개변수를 가릴 수 있습니다.
fn inner_lifetime<'a>() {} // OK
fn inner_type<T>() {} // OK
fn inner_const<const N: usize>() {} // OK
}
}
#![allow(unused)]
fn main() {
trait SomeTrait<'a, T, const N: usize> {
fn example_lifetime<'a>() {} // 오류: 'a는 이미 사용 중입니다
fn example_type<T>() {} // 오류: T는 이미 사용 중입니다
fn example_const<const N: usize>() {} // 오류: N은 이미 사용 중입니다
fn example_mixed<const T: usize>() {} // 오류: T는 이미 사용 중입니다
}
}
라이프타임 스코프
Lifetime parameters are declared in a GenericParams list and higher-ranked trait bounds.
‘static라이프타임과 [플레이스홀더 라이프타임](../lifetime-elision.md)’_` 는 특별한 의미를 가지며 매개변수로 선언될 수 없습니다.
라이프타임 제네릭 매개변수 스코프
상수 및 정적 아이템과 const 컨텍스트 는 오직 'static 라이프타임 참조만 허용하므로, 그 안에는 다른 어떤 라이프타임도 스코프에 있을 수 없습니다. 연관 상수 는 트레잇이나 구현에 선언된 라이프타임을 참조하는 것을 허용합니다.
고차 트레잇 바운드 스코프
고차 트레잇 바운드 로 선언된 라이프타임 매개변수의 스코프는 그것이 사용되는 시나리오에 따라 다릅니다.
- As a TypeBoundWhereClauseItem the declared lifetimes are in scope in the type and the type bounds.
- As a TraitBound the declared lifetimes are in scope within the bound type path.
- As a BareFunctionType the declared lifetimes are in scope within the function parameters and return type.
#![allow(unused)]
fn main() {
trait Trait<'a>{}
fn where_clause<T>()
// 'a는 타입과 타입 바운드 모두에서 스코프에 있습니다.
where for <'a> &'a T: Trait<'a>
{}
fn bound<T>()
// 'a는 바운드 내에서 스코프에 있습니다.
where T: for <'a> Trait<'a>
{}
struct Example<'a> {
field: &'a u32
}
// 'a는 매개변수와 반환 타입 모두에서 스코프에 있습니다.
type FnExample = for<'a> fn(x: Example<'a>) -> Example<'a>;
}
Impl 트레잇 제약
Impl 트레잇 타입은 함수나 구현에 선언된 라이프타임만 참조할 수 있습니다.
#![allow(unused)]
fn main() {
trait Trait1 {
type Item;
}
trait Trait2<'a> {}
struct Example;
impl Trait1 for Example {
type Item = Element;
}
struct Element;
impl<'a> Trait2<'a> for Element {}
// 여기의 `impl Trait2` 는 'b를 참조할 수 없지만 'a를 참조하는 것은
// 허용됩니다.
fn foo<'a>() -> impl for<'b> Trait1<Item = impl Trait2<'a> + use<'a>> {
// ...
Example
}
}
루프 레이블 스코프
루프 레이블 은 루프 표현식 에 의해 선언될 수 있습니다. 루프 레이블의 스코프는 선언된 지점부터 루프 표현식이 끝날 때까지입니다. 스코프는 아이템, 클로저, 비동기 블록, 상수 인수, const 컨텍스트, 그리고 정의하는 for 루프 의 반복자 표현식으로 확장되지 않습니다.
#![allow(unused)]
fn main() {
'a: for n in 0..3 {
if n % 2 == 0 {
break 'a;
}
fn inner() {
// 여기서 'a를 사용하는 것은 오류입니다.
// break 'a;
}
}
// 레이블은 `while` 루프의 표현식에 대한 스코프에 있습니다.
'a: while break 'a {} // 루프가 실행되지 않습니다.
'a: while let _ = break 'a {} // 루프가 실행되지 않습니다.
// 레이블은 정의하는 `for` 루프에서 스코프에 있지 않습니다:
'a: for outer in 0..5 {
// 이것은 외부 루프를 중단하여 내부 루프를 건너뛰고
// 외부 루프를 멈춥니다.
'a: for inner in { break 'a; 0..1 } {
println!("{}", inner); // 이것은 실행되지 않습니다.
}
println!("{}", outer); // 이것도 실행되지 않습니다.
}
}
루프 레이블은 외부 스코프에 있는 같은 이름의 레이블을 가릴 수 있습니다. 레이블에 대한 참조는 가장 가까운 정의를 가리킵니다.
#![allow(unused)]
fn main() {
// 루프 레이블 가림 예제.
'a: for outer in 0..5 {
'a: for inner in 0..5 {
// 이것은 내부 루프를 종료하지만, 외부 루프는 계속 실행됩니다.
break 'a;
}
}
}
프렐류드 스코프
프렐류드 는 엔티티를 모든 모듈의 스코프로 가져옵니다. 엔티티는 모듈의 멤버는 아니지만, 이름 해석 중에 암시적으로 조회됩니다.
프렐류드 이름은 모듈 내의 선언에 의해 가려질 수 있습니다.
프렐류드는 계층화되어 있어 동일한 이름의 엔티티를 포함할 경우 하나가 다른 하나를 가립니다. 프렐류드가 다른 프렐류드를 가릴 수 있는 순서는 다음과 같으며, 앞의 항목이 뒤의 항목을 가릴 수 있습니다:
macro_rules 스코프
The scope of macro_rules macros is described in the Macros By Example chapter. The behavior depends on the use of the macro_use and macro_export attributes.
파생 매크로 헬퍼 속성
Derive 매크로 도우미 속성 은 해당 derive 속성 이 지정된 아이템 내에서 스코프에 있습니다. 스코프는 derive 속성 직후부터 아이템의 끝까지 확장됩니다.
도우미 속성은 스코프 내의 같은 이름을 가진 다른 속성을 가립니다.
Self 스코프
Self 는 특별한 의미를 가진 키워드이지만, 일반 이름과 유사한 방식으로 이름 확인과 상호 작용합니다.
구조체, 열거형, 유니온, 트레잇, 또는 구현 의 정의에 있는 암시적 Self 타입은 제네릭 매개변수 와 유사하게 처리되며, 제네릭 타입 매개변수와 동일한 방식으로 스코프에 있습니다.
구현 의 값 네임스페이스 에 있는 암시적 Self 생성자는 구현의 본문(구현의 연관 아이템) 내에서 스코프에 있습니다.
#![allow(unused)]
fn main() {
// 구조체 정의 내의 Self 타입.
struct Recursive {
f1: Option<Box<Self>>
}
// 제네릭 매개변수 내의 Self 타입.
struct SelfGeneric<T: Into<Self>>(T);
// 구현 내의 Self 값 생성자.
struct ImplExample();
impl ImplExample {
fn example() -> Self { // Self 타입
Self() // Self 값 생성자
}
}
}
프렐류드
프렐류드(prelude) 는 크레이트의 모든 모듈 스코프에 자동으로 도입되는 이름들의 집합입니다.
이러한 프렐류드 이름은 모듈 자체의 일부가 아닙니다. 이들은 이름 확인 중에 암시적으로 조회됩니다. 예를 들어, Box 와 같은 것이 모든 모듈의 스코프에 있더라도, 현재 모듈의 멤버가 아니기 때문에 self::Box 로 참조할 수 없습니다.
여러 가지 다른 프렐류드가 있습니다:
표준 라이브러리 프렐류드
각 크레이트에는 단일 표준 라이브러리 모듈의 이름으로 구성된 표준 라이브러리 프렐류드가 있습니다.
사용되는 모듈은 크레이트의 에디션과 크레이트에 no_std 속성 이 적용되었는지 여부에 따라 달라집니다.
| 에디션 | no_std 적용되지 않음 | no_std 적용됨 |
|---|---|---|
| 2015 | std::prelude::rust_2015 | core::prelude::rust_2015 |
| 2018 | std::prelude::rust_2018 | core::prelude::rust_2018 |
| 2021 | std::prelude::rust_2021 | core::prelude::rust_2021 |
| 2024 | std::prelude::rust_2024 | core::prelude::rust_2024 |
Note
std::prelude::rust_2015및std::prelude::rust_2018은std::prelude::v1과 동일한 내용을 가집니다.
core::prelude::rust_2015및core::prelude::rust_2018은core::prelude::v1과 동일한 내용을 가집니다.
Note
When one of
core::panic!orstd::panic!is brought into scope due to the standard library prelude, and a user-written glob import brings the other into scope,rustccurrently allows use ofpanic!, even though it is ambiguous. The user-written glob import takes precedence to resolve this ambiguity.For details, see names.resolution.expansion.imports.ambiguity.panic-hack.
외부 프렐류드
루트 모듈에서 extern crate 로 임포트되거나 컴파일러에 제공된(예: rustc 의 --extern 플래그) 외부 크레이트들은 외부 프렐류드 에 추가됩니다. 만약 extern crate orig_name as new_name 과 같이 별칭으로 임포트했다면, 대신 new_name 심볼이 프렐류드에 추가됩니다.
core 크레이트는 항상 외부 프렐류드에 추가됩니다.
크레이트 루트에 no_std 속성 이 지정되지 않은 한 std 크레이트가 추가됩니다.
2018 Edition differences
In the 2015 edition, crates in the extern prelude cannot be referenced via use declarations, so it is generally standard practice to include
extern cratedeclarations to bring them into scope.2018 에디션부터 use 선언 은 외부 프렐류드의 크레이트를 참조할 수 있으므로,
extern crate를 사용하는 것은 관용적이지(unidiomatic) 않은 것으로 간주됩니다.
Note
Additional crates that ship with
rustc, such asalloc, andtest, are not automatically included with the--externflag when using Cargo. They must be brought into scope with anextern cratedeclaration, even in the 2018 edition.#![allow(unused)] fn main() { extern crate alloc; use alloc::rc::Rc; }Cargo는 오직 절차적 매크로(proc-macro) 크레이트에 대해서만
proc_macro를 외부 프렐류드로 가져옵니다.
no_std 속성
The no_std attribute causes the std crate to not be linked automatically and the standard library prelude to instead use the core prelude.
Example
#![no_std]
Note
Using
no_stdis useful when either the crate is targeting a platform that does not support the standard library or is purposefully not using the capabilities of the standard library. Those capabilities are mainly dynamic memory allocation (e.g.BoxandVec) and file and network capabilities (e.g.std::fsandstd::io).
Warning
Using
no_stddoes not prevent the standard library from being linked. It is still valid to writeextern crate stdin the crate or in one of its dependencies; this will cause the compiler to link thestdcrate into the program.
The no_std attribute uses the MetaWord syntax.
The no_std attribute may only be applied to the crate root.
The no_std attribute may be used any number of times on a form.
Note
rustclints against any use following the first.
The no_std attribute changes the standard library prelude to use the core prelude instead of the std prelude.
2018 Edition differences
Before the 2018 edition,
stdis injected into the crate root by default. Ifno_stdis specified,coreis injected instead. Starting with the 2018 edition, regardless ofno_stdbeing specified, neither is injected into the crate root.
언어 프렐류드
언어 프렐류드에는 언어에 내장된 타입과 속성들의 이름이 포함됩니다. 언어 프렐류드는 항상 스코프에 있습니다.
여기에는 다음이 포함됩니다:
macro_use 프렐류드
macro_use 프렐류드에는 extern crate 에 적용된 macro_use 속성 을 통해 임포트된 외부 크레이트의 매크로들이 포함됩니다.
도구 프렐류드
도구 프렐류드에는 타입 네임스페이스 에 있는 외부 도구들의 이름이 포함됩니다. 자세한 내용은 도구 속성 섹션을 참조하십시오.
no_implicit_prelude 속성
The no_implicit_prelude attribute is used to prevent implicit preludes from being brought into scope.
Example
#![allow(unused)] fn main() { // The attribute can be applied to the crate root to affect // all modules. #![no_implicit_prelude] // Or it can be applied to a module to only affect that module // and its descendants. #[no_implicit_prelude] mod example { // ... } }
The no_implicit_prelude attribute uses the MetaWord syntax.
The no_implicit_prelude attribute may only be applied to the crate or to a module.
Note
rustcignores use in other positions but lints against it. This may become an error in the future.
The no_implicit_prelude attribute may be used any number of times on a form.
Note
rustclints against any use following the first.
The no_implicit_prelude attribute prevents the standard library prelude, extern prelude, macro_use prelude, and the tool prelude from being brought into scope for the module and its descendants.
Note
Despite
#![no_implicit_prelude],rustccurrently brings certain macros implicitly into scope. Those macros are:
assert!cfg!cfg_select!column!compile_error!concat!concat_bytes!env!file!format_args!include!include_bytes!include_str!line!module_path!option_env!panic!stringify!unreachable!E.g., this works:
#![no_implicit_prelude] fn main() { assert!(true); }Don’t rely on this behavior; it may be removed in the future. Always bring the items you need into scope explicitly when using
#![no_implicit_prelude].For details, see Rust PR #62086 and Rust PR #139493.
The no_implicit_prelude attribute does not affect the language prelude.
2018 Edition differences
In the 2015 edition, the
no_implicit_preludeattribute does not affect themacro_useprelude, and all macros exported from the standard library are still included in themacro_useprelude. Starting in the 2018 edition, the attribute does remove themacro_useprelude.
경로
경로(path) 는 :: 토큰으로 구분된 하나 이상의 경로 세그먼트 시퀀스입니다. 경로는 아이템, 값, 타입, 매크로 및 속성 을 참조하는 데 사용됩니다.
식별자 세그먼트로만 구성된 단순 경로의 두 가지 예:
x;
x::y::z;
경로의 종류
Simple paths
Syntax
SimplePath →
::? SimplePathSegment ( :: SimplePathSegment )*
SimplePathSegment →
IDENTIFIER | super | self | crate | $crate
단순 경로는 가시성 표시어, 속성, 매크로 및 use 아이템에서 사용됩니다. 예를 들어:
#![allow(unused)]
fn main() {
use std::io::{self, Write};
mod m {
#[clippy::cyclomatic_complexity = "0"]
pub (in super) fn f1() {}
}
}
표현식에서의 경로
Syntax
PathInExpression →
::? PathExprSegment ( :: PathExprSegment )*
PathExprSegment →
PathIdentSegment ( :: GenericArgs )?
PathIdentSegment →
IDENTIFIER | super | self | Self | crate | $crate
GenericArgs →
< >
| < ( GenericArg , )* GenericArg ,? >
GenericArg →
Lifetime | Type | GenericArgsConst | GenericArgsBinding | GenericArgsBounds
GenericArgsConst →
BlockExpression
| LiteralExpression
| - LiteralExpression
| SimplePathSegment
GenericArgsBinding →
IDENTIFIER GenericArgs? = Type
GenericArgsBounds →
IDENTIFIER GenericArgs? : TypeParamBounds
표현식에서의 경로는 제네릭 인자가 포함된 경로를 지정할 수 있게 해줍니다. 이들은 표현식 과 패턴 의 다양한 곳에서 사용됩니다.
제네릭 인자를 위한 여는 < 앞에는 작음(less-than) 연산자와의 모호성을 피하기 위해 :: 토큰이 필요합니다. 이것은 구어체로 “터보피쉬(turbofish)” 구문이라고 알려져 있습니다.
#![allow(unused)]
fn main() {
(0..10).collect::<Vec<_>>();
Vec::<u8>::with_capacity(1024);
}
제네릭 인자의 순서는 라이프타임 인자, 그 다음 타입 인자, 그 다음 상수 인자, 그 다음 등치 제약 조건(equality constraints) 순으로 제한됩니다.
Const arguments must be surrounded by braces unless they are a literal, an inferred const, or a single segment path. An inferred const may not be surrounded by braces.
#![allow(unused)]
fn main() {
mod m {
pub const C: usize = 1;
}
const C: usize = m::C;
fn f<const N: usize>() -> [u8; N] { [0; N] }
let _ = f::<1>(); // Literal.
let _: [_; 1] = f::<_>(); // Inferred const.
let _: [_; 1] = f::<(((_)))>(); // Inferred const.
let _ = f::<C>(); // Single segment path.
let _ = f::<{ m::C }>(); // Multi-segment path must be braced.
}
#![allow(unused)]
fn main() {
fn f<const N: usize>() -> [u8; N] { [0; _] }
let _: [_; 1] = f::<{ _ }>();
// ^ ERROR `_` not allowed here
}
Note
In a generic argument list, an inferred const is parsed as an inferred type but then semantically treated as a separate kind of const generic argument.
impl Trait 타입에 대응하는 합성 타입 파라미터(synthetic type parameters)는 암시적이며, 이를 명시적으로 지정할 수 없습니다.
정규화된 경로(Qualified paths)
Syntax
QualifiedPathInExpression → QualifiedPathType ( :: PathExprSegment )+
QualifiedPathType → < Type ( as TypePath )? >
QualifiedPathInType → QualifiedPathType ( :: TypePathSegment )+
완전하게 정규화된 경로(Fully qualified paths)는 트레잇 구현 에 대한 경로의 모호성을 제거하고 표준 경로(canonical paths) 를 지정할 수 있게 해줍니다. 타입 명세에서 사용될 때는 아래에 명시된 타입 구문을 사용하는 것을 지원합니다.
#![allow(unused)]
fn main() {
struct S;
impl S {
fn f() { println!("S"); }
}
trait T1 {
fn f() { println!("T1 f"); }
}
impl T1 for S {}
trait T2 {
fn f() { println!("T2 f"); }
}
impl T2 for S {}
S::f(); // 내재적 구현(inherent impl)을 호출합니다.
<S as T1>::f(); // T1 트레잇 함수를 호출합니다.
<S as T2>::f(); // T2 트레잇 함수를 호출합니다.
}
타입에서의 경로
Syntax
TypePath → ::? TypePathSegment ( :: TypePathSegment )*
TypePathSegment → PathIdentSegment ( ::? ( GenericArgs | TypePathFn ) )?
TypePathFn → ( TypePathFnInputs? ) ( -> TypeNoBounds )?
TypePathFnInputs → Type ( , Type )* ,?
타입 경로는 타입 정의, 트레잇 바운드, 타입 파라미터 바운드 및 정규화된 경로 내에서 사용됩니다.
Although the :: token is allowed before the generics arguments, it is not required because there is no ambiguity like there is in PathInExpression.
#![allow(unused)]
fn main() {
mod ops {
pub struct Range<T> {f1: T}
pub trait Index<T> {}
pub struct Example<'a> {f1: &'a i32}
}
struct S;
impl ops::Index<ops::Range<usize>> for S { /*...*/ }
fn i<'a>() -> impl Iterator<Item = ops::Example<'a>> {
// ...
const EXAMPLE: Vec<ops::Example<'static>> = Vec::new();
EXAMPLE.into_iter()
}
type G = std::boxed::Box<dyn std::ops::FnOnce(isize) -> isize>;
}
경로 한정자(Path qualifiers)
경로는 해석 방식의 의미를 변경하기 위해 앞에 다양한 한정자를 붙여 표시할 수 있습니다.
Note
usedeclarations have additional behaviors and restrictions forself,super,crate, and$crate.
::
:: 로 시작하는 경로는 전역 경로(global paths) 로 간주되며, 경로의 세그먼트가 해석되기 시작하는 위치는 에디션에 따라 다릅니다. 경로의 각 식별자는 아이템으로 해석되어야 합니다.
2018 Edition differences
In the 2015 Edition, identifiers resolve from the “crate root” (
crate::in the 2018 edition), which contains a variety of different items, including external crates, default crates such asstdorcore, and items in the top level of the crate (includinguseimports).2018 에디션부터
::로 시작하는 경로는 외부 프렐류드(extern prelude) 에 있는 크레이트에서 해석됩니다. 즉, 그 뒤에는 반드시 크레이트 이름이 와야 합니다.
#![allow(unused)]
fn main() {
pub fn foo() {
// 2018 에디션에서는 외부 프렐류드를 통해 `std` 에 접근합니다.
// 2015 에디션에서는 크레이트 루트를 통해 `std` 에 접근합니다.
let now = ::std::time::Instant::now();
println!("{:?}", now);
}
}
// 2015 에디션
mod a {
pub fn foo() {}
}
mod b {
pub fn foo() {
::a::foo(); // `a` 의 foo 함수를 호출합니다.
// Rust 2018에서 `::a` 는 크레이트 `a` 로 해석됩니다.
}
}
fn main() {}
self
self 는 현재 모듈을 기준으로 상대적인 경로를 해석합니다.
self 는 앞에 :: 없이 오직 첫 번째 세그먼트로만 사용될 수 있습니다.
메서드 본문에서 단일 self 세그먼트로 구성된 경로는 해당 메서드의 self 파라미터로 해석됩니다.
fn foo() {}
fn bar() {
self::foo();
}
struct S(bool);
impl S {
fn baz(self) {
self.0;
}
}
fn main() {}
Self
대문자 “S“로 시작하는 Self 는 현재 구현되거나 정의되고 있는 타입을 참조하는 데 사용됩니다. 다음과 같은 상황에서 사용될 수 있습니다:
- 트레잇(trait) 정의에서, 이는 해당 트레잇을 구현하는 타입을 참조합니다.
- 구현(implementation) 에서, 이는 구현되고 있는 타입을 참조합니다. 튜플 또는 유닛 구조체(struct) 를 구현할 때는 값 네임스페이스 의 생성자도 참조합니다.
- 구조체, 열거형(enumeration) 또는 공용체(union) 의 정의에서, 이는 정의되고 있는 타입을 참조합니다. 정의가 무한히 재귀적일 수는 없습니다(반드시 간접 참조(indirection)가 있어야 합니다).
Self 의 스코프는 제네릭 파라미터와 유사하게 동작합니다. 자세한 내용은 Self 스코프 섹션을 참조하십시오.
Self 는 앞에 :: 없이 오직 첫 번째 세그먼트로만 사용될 수 있습니다.
Self 경로는 (Self::<i32> 와 같이) 제네릭 인자를 포함할 수 없습니다.
#![allow(unused)]
fn main() {
trait T {
type Item;
const C: i32;
// `Self` 는 `T` 를 구현하는 어떤 타입이든 될 수 있습니다.
fn new() -> Self;
// `Self::Item` 은 구현체에서의 타입 별칭(type alias)이 됩니다.
fn f(&self) -> Self::Item;
}
struct S;
impl T for S {
type Item = i32;
const C: i32 = 9;
fn new() -> Self { // `Self` 는 `S` 타입입니다.
S
}
fn f(&self) -> Self::Item { // `Self::Item` 은 `i32` 타입입니다.
Self::C // `Self::C` 는 상수 값 `9` 입니다.
}
}
// `Self` 는 트레잇 정의의 제네릭 내에서 스코프에 있으며,
// 정의되고 있는 타입을 참조합니다.
trait Add<Rhs = Self> {
type Output;
// `Self` 는 또한 구현되고 있는 타입의
// 연관 아이템을 참조할 수 있습니다.
fn add(self, rhs: Rhs) -> Self::Output;
}
struct NonEmptyList<T> {
head: T,
// 구조체는 자기 자신을 참조할 수 있습니다(무한히
// 재귀적이지 않은 한).
tail: Option<Box<Self>>,
}
}
super
경로에서의 super 는 부모 모듈로 해석됩니다.
이는 경로의 앞부분 세그먼트에서만 사용될 수 있으며, 초기 self 세그먼트 뒤에 올 수도 있습니다.
mod a {
pub fn foo() {}
}
mod b {
pub fn foo() {
super::a::foo(); // a의 foo 함수를 호출합니다.
}
}
fn main() {}
super 는 조상 모듈을 참조하기 위해 첫 번째 super 또는 self 뒤에 여러 번 반복될 수 있습니다.
mod a {
fn foo() {}
mod b {
mod c {
fn foo() {
super::super::foo(); // a의 foo 함수를 호출합니다.
self::super::super::foo(); // a의 foo 함수를 호출합니다.
}
}
}
}
fn main() {}
crate
crate 는 현재 크레이트를 기준으로 상대적인 경로를 해석합니다.
crate 는 앞에 :: 없이 오직 첫 번째 세그먼트로만 사용될 수 있습니다.
fn foo() {}
mod a {
fn bar() {
crate::foo();
}
}
fn main() {}
$crate
$crate is only used within macro transcribers, and can only be used as the first segment, without a preceding ::.
$crate will expand to a path to access items from the top level of the crate where the macro is defined, regardless of which crate the macro is invoked.
pub fn increment(x: u32) -> u32 {
x + 1
}
#[macro_export]
macro_rules! inc {
($x:expr) => ( $crate::increment($x) )
}
fn main() { }
표준 경로(Canonical paths)
Each item defined in a module or implementation has a canonical path that corresponds to where within its crate it is defined.
이러한 아이템들에 대한 다른 모든 경로들은 별칭(aliases)입니다.
표준 경로는 아이템 자체가 정의하는 경로 세그먼트가 추가된 경로 접두사(path prefix) 로 정의됩니다.
구현체(Implementations) 와 use 선언(use declarations) 은 표준 경로를 갖지 않지만, 구현체가 정의하는 아이템들은 표준 경로를 갖습니다. 블록 표현식에 정의된 아이템은 표준 경로를 갖지 않습니다. 표준 경로가 없는 모듈에 정의된 아이템은 표준 경로를 갖지 않습니다. 표준 경로가 없는 아이템(예: 구현하는 타입, 구현되는 트레잇, 타입 파라미터 또는 타입 파라미터의 바운드)을 참조하는 구현체에 정의된 연관 아이템들은 표준 경로를 갖지 않습니다.
모듈의 경로 접두사는 해당 모듈에 대한 표준 경로입니다.
단순 구현체(bare implementations)의 경우, 구현되고 있는 아이템의 표준 경로를 화살괄호(<>)로 둘러싼 형태가 접두사가 됩니다.
트레잇 구현체(trait implementations) 의 경우, 구현되고 있는 아이템의 표준 경로 뒤에 as 와 트레잇의 표준 경로를 붙인 후 전체를 화살괄호(<>)로 둘러싼 형태가 접두사가 됩니다.
표준 경로는 오직 주어진 크레이트 내에서만 의미가 있습니다. 크레이트들 사이에 공통된 전역 네임스페이스는 존재하지 않습니다. 아이템의 표준 경로는 단지 크레이트 내에서 아이템을 식별할 뿐입니다.
// 주석은 아이템의 표준 경로를 나타냅니다.
mod a { // crate::a
pub struct Struct; // crate::a::Struct
pub trait Trait { // crate::a::Trait
fn f(&self); // crate::a::Trait::f
}
impl Trait for Struct {
fn f(&self) {} // <crate::a::Struct as crate::a::Trait>::f
}
impl Struct {
fn g(&self) {} // <crate::a::Struct>::g
}
}
mod without { // crate::without
fn canonicals() { // crate::without::canonicals
struct OtherStruct; // 없음(None)
trait OtherTrait { // 없음(None)
fn g(&self); // 없음(None)
}
impl OtherTrait for OtherStruct {
fn g(&self) {} // 없음(None)
}
impl OtherTrait for crate::a::Struct {
fn g(&self) {} // 없음(None)
}
impl crate::a::Trait for OtherStruct {
fn f(&self) {} // 없음(None)
}
}
}
fn main() {}
이름 확인
Name resolution is the process of tying paths and other identifiers to the declarations of those entities. Names are segregated into different namespaces, allowing entities in different namespaces to share the same name without conflict. Each name is valid within a scope, or a region of source text where that name may be referenced. Access to a name may be restricted based on its visibility.
Name resolution is split into three stages throughout the compilation process. The first stage, expansion-time resolution, resolves all use declarations and macro invocations. The second stage, primary resolution, resolves all names that have not yet been resolved and that do not depend on type information to resolve. The last stage, type-relative resolution, resolves the remaining names once type information is available.
Note
Expansion-time resolution is also known as early resolution. Primary resolution is also known as late resolution.
General
The rules within this section apply to all stages of name resolution.
스코프
Note
This is a placeholder for future expansion about resolution of names within various scopes.
Expansion-time name resolution
Expansion-time name resolution is the stage of name resolution necessary to complete macro expansion and fully generate a crate’s AST. This stage requires the resolution of macro invocations and use declarations. Resolving use declarations is required for macro invocations that resolve via path-based scope. Resolving macro invocations is required in order to expand them.
After expansion-time name resolution, the AST must not contain any unexpanded macro invocations. Every macro invocation resolves to a valid definition that exists in the final AST or in an external crate.
#![allow(unused)]
fn main() {
m!(); // ERROR: Cannot find macro `m` in this scope.
}
The resolution of names must be stable. After expansion, names in the fully expanded AST must resolve to the same definition regardless of the order in which macros are expanded and imports are resolved.
All name resolution candidates selected during macro expansion are considered speculative. Once the crate has been fully expanded, all speculative import resolutions are validated to ensure that macro expansion did not introduce any new ambiguities.
Note
Due to the iterative nature of macro expansion, this causes so-called time traveling ambiguities, such as when a macro or glob import introduces an item that is ambiguous with its own base path.
fn main() {} macro_rules! f { () => { mod m { pub(crate) use f; } } } f!(); const _: () = { // Initially, we speculatively resolve `m` to the module in // the crate root. // // Expansion of `f` introduces a second `m` module inside this // body. // // Expansion-time resolution finalizes resolutions by re- // resolving all imports and macro invocations, sees the // introduced ambiguity and reports it as an error. m::f!(); // ERROR: `m` is ambiguous. };
Imports
All use declarations are fully resolved during this stage of resolution. Type-relative paths cannot be resolved at this stage and will produce an error.
#![allow(unused)]
fn main() {
mod m {
pub const C: () = ();
pub enum E { V }
pub type A = E;
impl E {
pub const C: () = ();
}
}
// Valid imports resolved at expansion-time:
use m::C; // OK.
use m::E; // OK.
use m::A; // OK.
use m::E::V; // OK.
// Valid expressions resolved during type-relative resolution:
let _ = m::A::V; // OK.
let _ = m::E::C; // OK.
}
#![allow(unused)]
fn main() {
mod m {
pub const C: () = ();
pub enum E { V }
pub type A = E;
impl E {
pub const C: () = ();
}
}
// Invalid type-relative imports that can't resolve at expansion-time:
use m::A::V; // ERROR: Unresolved import `m::A::V`.
use m::E::C; // ERROR: Unresolved import `m::E::C`.
}
Names introduced via use declarations in an outer scope are shadowed by candidates in the same namespace with the same name from an inner scope except where otherwise restricted by name resolution ambiguities.
#![allow(unused)]
fn main() {
pub mod m1 {
pub mod ambig {
pub const C: u8 = 1;
}
}
pub mod m2 {
pub mod ambig {
pub const C: u8 = 2;
}
}
// This introduces the name `ambig` in the outer scope.
use m1::ambig;
const _: () = {
// This shadows `ambig` in the inner scope.
use m2::ambig;
// The inner candidate is selected here
// as the resolution of `ambig`.
use ambig::C;
assert!(C == 2);
};
}
Shadowing of names introduced via use declarations within a single scope is permitted in the following situations:
모호성
There are certain situations during expansion-time resolution where there are multiple macro definitions, use declarations, or modules an import or macro invocation’s name could refer to where the compiler cannot consistently determine which candidate should shadow the other. Shadowing cannot be permitted in these situations and the compiler instead emits ambiguity errors.
Names may not be resolved through ambiguous glob imports. Glob imports are allowed to import conflicting names in the same namespace as long as the name is not used. Names with conflicting candidates from ambiguous glob imports may still be shadowed by non-glob imports and used without producing an error. The errors occur at time of use, not time of import.
#![allow(unused)]
fn main() {
mod m1 {
pub struct Ambig;
}
mod m2 {
pub struct Ambig;
}
// OK: This brings conficting names in the same namespace into scope
// but they have not been used yet.
use m1::*;
use m2::*;
const _: () = {
// The error happens when the name with the conflicting candidates
// is used.
let x = Ambig; // ERROR: `Ambig` is ambiguous.
};
}
#![allow(unused)]
fn main() {
mod m1 {
pub struct Ambig;
}
mod m2 {
pub struct Ambig;
}
use m1::*;
use m2::*; // OK: No name conflict.
const _: () = {
// This is permitted, since resolution is not through the
// ambiguous globs.
struct Ambig;
let x = Ambig; // OK.
};
}
Multiple glob imports are allowed to import the same name, and that name is allowed to be used if the imports are of the same item (following reexports). The visibility of the name is the maximum visibility of the imports.
mod m1 {
pub struct Ambig;
}
mod m2 {
// This reexports the same `Ambig` item from a second module.
pub use super::m1::Ambig;
}
mod m3 {
// These both import the same `Ambig`.
//
// The visibility of `Ambig` is `pub` because that is the
// maximum visibility between these two `use` declarations.
pub use super::m1::*;
use super::m2::*;
}
mod m4 {
// `Ambig` can be used through the `m3` globs and still has
// `pub` visibility.
pub use crate::m3::Ambig;
}
const _: () = {
// Therefore, we can use it here.
let _ = m4::Ambig; // OK.
};
fn main() {}
Names in imports and macro invocations may not be resolved through glob imports when there is another candidate available in an outer scope.
Note
When one of
core::panic!orstd::panic!is brought into scope due to the standard library prelude, and a user-written glob import brings the other into scope,rustccurrently allows use ofpanic!, even though it is ambiguous. The user-written glob import takes precedence to resolve this ambiguity.In Rust 2021 and later,
core::panic!andstd::panic!operate identically. But in earlier editions, they differ; onlystd::panic!accepts aStringas the format argument.E.g., this is an error:
extern crate core; use ::core::prelude::v1::*; fn main() { panic!(std::string::String::new()); // ERROR. }And this is accepted:
#![no_std] extern crate std; use ::std::prelude::v1::*; fn main() { panic!(std::string::String::new()); // OK. }Don’t rely on this behavior; the plan is to remove it.
For details, see Rust issue #147319.
#![allow(unused)]
fn main() {
mod glob {
pub mod ambig {
pub struct Name;
}
}
// Outer `ambig` candidate.
pub mod ambig {
pub struct Name;
}
const _: () = {
// Cannot resolve `ambig` through this glob
// because of the outer `ambig` candidate above.
use glob::*;
use ambig::Name; // ERROR: `ambig` is ambiguous.
};
}
#![allow(unused)]
fn main() {
// As above, but with macros.
pub mod m {
macro_rules! f {
() => {};
}
pub(crate) use f;
}
pub mod glob {
macro_rules! f {
() => {};
}
pub(crate) use f as ambig;
}
use m::f as ambig;
const _: () = {
use glob::*;
ambig!(); // ERROR: `ambig` is ambiguous.
};
}
Note
These ambiguity errors are specific to expansion-time resolution. Having multiple candidates available for a given name during later stages of resolution is not considered an error. So long as none of the imports themselves are ambiguous, there will always be a single unambiguous closest resolution.
#![allow(unused)] fn main() { mod glob { pub const AMBIG: u8 = 1; } mod outer { pub const AMBIG: u8 = 2; } use outer::AMBIG; const C: () = { use glob::*; assert!(AMBIG == 1); // ^---- This `AMBIG` is resolved during primary resolution. }; }
Names may not be resolved through ambiguous macro reexports. Macro reexports are ambiguous when they would shadow a textual macro candidate for the same name in an outer scope.
#![allow(unused)]
fn main() {
// Textual macro candidate.
macro_rules! ambig {
() => {}
}
// Path-based macro candidate.
macro_rules! path_based {
() => {}
}
pub fn f() {
// This reexport of the `path_based` macro definition
// as `ambig` may not shadow the `ambig` macro definition
// which is resolved via textual macro scope.
use path_based as ambig;
ambig!(); // ERROR: `ambig` is ambiguous.
}
}
Note
This restriction is needed due to implementation details in the compiler, specifically the current scope visitation logic and the complexity of supporting this behavior. This ambiguity error may be removed in the future.
매크로
Macros are resolved by iterating through the available scopes to find the available candidates. Macros are split into two sub-namespaces, one for function-like macros, and the other for attributes and derives. Resolution candidates from the incorrect sub-namespace are ignored.
The available scope kinds are visited in the following order. Each of these scope kinds represent one or more scopes.
- Derive helpers
- Textual scope macros
- Path-based scope macros
macro_useprelude- Standard library prelude
- Builtin attributes
Note
The compiler will attempt to resolve derive helpers that are used before their associated macro introduces them into scope. This scope is visited after the scope for resolving derive helper candidates that are correctly in scope. This behavior is slated for removal.
For more info see derive helper scope.
Note
This visitation order may change in the future, such as interleaving the visitation of textual and path-based scope candidates based on their lexical scopes.
2018 Edition differences
Starting in edition 2018 the
#[macro_use]prelude is not visited when#[no_implicit_prelude]is present.
The names cfg and cfg_attr are reserved in the macro attribute sub-namespace.
모호성
Names may not be resolved through ambiguous candidates inside of macro expansions. Candidates inside of macro expansions are ambiguous when they would shadow a candidate for the same name from outside of the first candidate’s macro expansion and the invocation of the name being resolved is also from outside of the first candidate’s macro expansion.
#![allow(unused)]
fn main() {
macro_rules! define_ambig {
() => {
macro_rules! ambig {
() => {}
}
}
}
// Introduce outer candidate definition for `ambig` macro invocation.
macro_rules! ambig {
() => {}
}
// Introduce a second candidate definition for `ambig` inside of a
// macro expansion.
define_ambig!();
// The definition of `ambig` from the second invocation
// of `define_ambig` is the innermost canadidate.
//
// The definition of `ambig` from the first invocation of
// `define_ambig` is the second candidate.
//
// The compiler checks that the first candidate is inside of a macro
// expansion, that the second candidate is not from within the same
// macro expansion, and that the name being resolved is not from
// within the same macro expansion.
ambig!(); // ERROR: `ambig` is ambiguous.
}
The reverse is not considered ambiguous.
#![allow(unused)]
fn main() {
macro_rules! define_ambig {
() => {
macro_rules! ambig {
() => {}
}
}
}
// Swap order of definitions.
define_ambig!();
macro_rules! ambig {
() => {}
}
// The innermost candidate is now less expanded so it may shadow more
// the macro expanded definition above it.
ambig!();
}
Nor is it ambiguous if the invocation being resolved is within the innermost candidate’s expansion.
#![allow(unused)]
fn main() {
macro_rules! ambig {
() => {}
}
macro_rules! define_and_invoke_ambig {
() => {
// Define innermost candidate.
macro_rules! ambig {
() => {}
}
// Invocation of `ambig` is in the same expansion as the
// innermost candidate.
ambig!(); // OK
}
}
define_and_invoke_ambig!();
}
It doesn’t matter if both definitions come from invocations of the same macro; the outermost candidate is still considered “less expanded” because it is not within the expansion containing the innermost candidate’s definition.
#![allow(unused)]
fn main() {
macro_rules! define_ambig {
() => {
macro_rules! ambig {
() => {}
}
}
}
define_ambig!();
define_ambig!();
ambig!(); // ERROR: `ambig` is ambiguous.
}
This also applies to imports so long as the innermost candidate for the name is from within a macro expansion.
#![allow(unused)]
fn main() {
macro_rules! define_ambig {
() => {
mod ambig {
pub struct Name;
}
}
}
mod ambig {
pub struct Name;
}
const _: () = {
// Introduce innermost candidate for
// `ambig` mod in this macro expansion.
define_ambig!();
use ambig::Name; // ERROR: `ambig` is ambiguous.
};
}
User-defined attributes or derive macros may not shadow built-in non-macro attributes (e.g. inline).
// with-helper/src/lib.rs
use proc_macro::TokenStream;
#[proc_macro_derive(WithHelperAttr, attributes(non_exhaustive))]
// ^^^^^^^^^^^^^^
// User-defined attribute candidate.
// ...
pub fn derive_with_helper_attr(_item: TokenStream) -> TokenStream {
TokenStream::new()
}
// src/lib.rs
#[derive(with_helper::WithHelperAttr)]
#[non_exhaustive] // ERROR: `non_exhaustive` is ambiguous.
struct S;
Note
This applies regardless of the name the built-in attribute is a candidate for:
// with-helper/src/lib.rs use proc_macro::TokenStream; #[proc_macro_derive(WithHelperAttr, attributes(helper))] // ^^^^^^ // User-defined attribute candidate. // ... pub fn derive_with_helper_attr(_item: TokenStream) -> TokenStream { TokenStream::new() }// src/lib.rs use inline as helper; // ^----- Built-in attribute candidate via reexport. #[derive(with_helper::WithHelperAttr)] #[helper] // ERROR: `helper` is ambiguous. struct S;
Primary name resolution
Note
This is a placeholder for future expansion about primary name resolution.
Type-relative resolution
Note
This is a placeholder for future expansion about type-dependent resolution.
가시성과 프라이버시
Syntax
Visibility →
pub
| pub ( crate )
| pub ( self )
| pub ( super )
| pub ( in SimplePath )
이 두 용어는 종종 혼용되어 사용되며, 이들이 전달하고자 하는 것은 “이 아이템을 이 위치에서 사용할 수 있는가?“라는 질문에 대한 답변입니다.
러스트의 이름 확인(name resolution)은 네임스페이스의 전역 계층 구조 위에서 작동합니다. 계층 구조의 각 레벨은 어떤 아이템으로 생각할 수 있습니다. 아이템은 위에서 언급된 것들 중 하나이거나 외부 크레이트도 포함합니다. 새로운 모듈을 선언하거나 정의하는 것은 정의된 위치의 계층 구조에 새로운 트리를 삽입하는 것으로 생각할 수 있습니다.
인터페이스가 모듈 간에 사용될 수 있는지 제어하기 위해, 러스트는 각 아이템의 사용이 허용되어야 하는지 여부를 확인합니다. 여기서 프라이버시 경고가 생성되거나, 그렇지 않으면 “다른 모듈의 비공개 아이템을 사용했으며 이는 허용되지 않습니다“라는 오류가 발생합니다.
기본적으로 모든 것은 비공개(private) 이며, 두 가지 예외가 있습니다: pub 트레잇의 연관 아이템은 기본적으로 공개이며, pub 열거형의 열거형 변형(Enum variants) 또한 기본적으로 공개입니다. 아이템이 pub 으로 선언되면 외부에서 접근 가능한 것으로 생각할 수 있습니다. 예를 들어:
fn main() {}
// 비공개(private) 구조체를 선언합니다.
struct Foo;
// 비공개 필드를 가진 공개(public) 구조체를 선언합니다.
pub struct Bar {
field: i32,
}
// 두 개의 공개 변형(variants)을 가진 공개 열거형을 선언합니다.
pub enum State {
PubliclyAccessibleState,
PubliclyAccessibleState2,
}
아이템이 공개 또는 비공개라는 개념과 함께, 러스트는 다음 두 가지 경우에 아이템 접근을 허용합니다:
- 아이템이 공개라면, 모듈
m에서 아이템의 모든 조상 모듈에 접근할 수 있는 경우m외부에서 해당 아이템에 접근할 수 있습니다. 또한 재내보내기(re-exports)를 통해 해당 아이템의 이름을 지정할 수도 있습니다. 아래를 참조하십시오. - 아이템이 비공개라면, 현재 모듈과 그 하위 모듈들에서만 접근할 수 있습니다.
이 두 가지 경우는 내부 구현 세부 사항을 숨기면서 공개 API를 노출하는 모듈 계층 구조를 만드는 데 매우 강력합니다. 설명을 돕기 위해 몇 가지 유스케이스와 그에 따른 결과를 소개합니다:
-
라이브러리 개발자는 자신의 라이브러리에 링크하는 크레이트들에 기능을 노출해야 합니다. 첫 번째 경우의 결과로서, 이는 외부에서 사용 가능한 모든 것이 루트부터 대상 아이템까지
pub이어야 함을 의미합니다. 체인의 어느 한 아이템이라도 비공개라면 외부 접근이 허용되지 않습니다. -
크레이트 내에서 전역적으로 사용 가능한 “도우미 모듈“이 필요하지만, 이를 공개 API로 노출하고 싶지 않을 수 있습니다. 이를 위해 크레이트 계층 구조의 루트에 비공개 모듈을 두고, 그 내부에 “공개 API“를 갖게 합니다. 크레이트 전체가 루트의 하위 모듈이므로, 두 번째 경우를 통해 로컬 크레이트 전체가 이 비공개 모듈에 접근할 수 있습니다.
-
모듈에 대한 유닛 테스트를 작성할 때, 테스트 대상 모듈의 직계 자식 모듈로
mod test를 두는 것이 일반적인 관례입니다. 이 모듈은 두 번째 경우를 통해 부모 모듈의 모든 아이템에 접근할 수 있으며, 이는 내부 구현 세부 사항도 자식 모듈에서 원활하게 테스트될 수 있음을 의미합니다.
두 번째 경우에서 비공개 아이템이 현재 모듈과 그 하위 모듈들에 의해 “접근될 수 있다“고 언급했는데, 아이템에 접근한다는 것의 정확한 의미는 아이템이 무엇인지에 따라 다릅니다.
예를 들어, 모듈에 접근한다는 것은 그 내부를 들여다보는 것(더 많은 아이템을 임포트하기 위해)을 의미합니다. 반면, 함수에 접근한다는 것은 그것을 호출하는 것을 의미합니다. 또한, 경로 표현식과 임포트 문은 대상이 현재 가시성 스코프 내에 있는 경우에만 유효하다는 의미에서 아이템에 접근하는 것으로 간주됩니다.
위에서 설명한 세 가지 경우를 보여주는 프로그램의 예입니다:
// 이 모듈은 비공개이므로 외부 크레이트가 접근할 수 없습니다.
// 하지만 이 현재 크레이트의 루트에서는 비공개이므로,
// 크레이트 내의 모든 모듈은 이 모듈 내의 공개적으로 표시되는 아이템에 접근할 수 있습니다.
mod crate_helper_module {
// 이 함수는 현재 크레이트 내의 무엇이든 사용할 수 있습니다.
pub fn crate_helper() {}
// 이 함수는 크레이트 내의 다른 어떤 곳에서도 사용할 수 *없습니다*.
// `crate_helper_module` 외부에서는 공개적으로 보이지 않으므로,
// 오직 이 현재 모듈과 그 하위 모듈들만 접근할 수 있습니다.
fn implementation_detail() {}
}
// 이 함수는 "루트에 공개"되어 있으므로 이 라이브러리에 링크하는
// 외부 크레이트가 사용할 수 있습니다.
pub fn public_api() {}
// 'public_api'와 마찬가지로 이 모듈은 공개되어 있어 외부 크레이트가
// 내부를 들여다볼 수 있습니다.
pub mod submodule {
use crate::crate_helper_module;
pub fn my_method() {
// 로컬 크레이트의 모든 아이템은 위의 두 규칙의 조합을 통해
// 도우미 모듈의 공개 인터페이스를 호출할 수 있습니다.
crate_helper_module::crate_helper();
}
// 이 함수는 `submodule` 의 하위 모듈이 아닌 모듈에는 숨겨집니다.
fn my_implementation() {}
#[cfg(test)]
mod test {
#[test]
fn test_my_implementation() {
// 이 모듈은 `submodule` 의 하위 모듈이므로 프라이버시 위반 없이
// `submodule` 내부의 비공개 아이템에 접근하는 것이 허용됩니다.
super::my_implementation();
}
}
}
fn main() {}
러스트 프로그램이 프라이버시 검사 단계를 통과하려면 모든 경로가 위에서 언급한 두 규칙에 따라 유효한 접근이어야 합니다. 여기에는 모든 use 문, 표현식, 타입 등이 포함됩니다.
pub(in path), pub(crate), pub(super), 그리고 pub(self)
공개와 비공개 외에도, 러스트는 아이템을 특정 스코프 내에서만 볼 수 있도록 선언할 수 있게 해줍니다. pub 제약 조건에 대한 규칙은 다음과 같습니다:
pub(in path)는 제공된path내에서 아이템을 볼 수 있게 합니다.path는 가시성이 선언되는 아이템의 조상 모듈로 해석되는 단순 경로(simple path)여야 합니다.path내의 각 식별자는 모듈을 직접 참조해야 합니다(use문에 의해 도입된 이름이 아니어야 합니다).
pub(crate)는 현재 크레이트 내에서 아이템을 볼 수 있게 합니다.
pub(super)는 부모 모듈에서 아이템을 볼 수 있게 합니다. 이는pub(in super)와 동일합니다.
pub(self)는 현재 모듈에서 아이템을 볼 수 있게 합니다. 이는pub(in self)와 동일하거나pub을 전혀 사용하지 않는 것과 같습니다.
2018 Edition differences
Starting with the 2018 edition, paths for
pub(in path)must start withcrate,self, orsuper. The 2015 edition may also use paths starting with::or modules from the crate root.
예제입니다:
pub mod outer_mod {
pub mod inner_mod {
// 이 함수는 `outer_mod` 내에서 볼 수 있습니다.
pub(in crate::outer_mod) fn outer_mod_visible_fn() {}
// 위와 동일하며, 2015 에디션에서만 유효합니다.
pub(in outer_mod) fn outer_mod_visible_fn_2015() {}
// 이 함수는 크레이트 전체에서 볼 수 있습니다.
pub(crate) fn crate_visible_fn() {}
// 이 함수는 `outer_mod` 내에서 볼 수 있습니다.
pub(super) fn super_mod_visible_fn() {
// 동일한 `mod` 에 있으므로 이 함수를 볼 수 있습니다.
inner_mod_visible_fn();
}
// 이 함수는 오직 `inner_mod` 내에서만 볼 수 있으며,
// 이는 비공개로 두는 것과 같습니다.
pub(self) fn inner_mod_visible_fn() {}
}
pub fn foo() {
inner_mod::outer_mod_visible_fn();
inner_mod::crate_visible_fn();
inner_mod::super_mod_visible_fn();
// `inner_mod` 외부에 있으므로 이 함수는 더 이상 보이지 않습니다.
// 오류! `inner_mod_visible_fn` 은 비공개입니다.
//inner_mod::inner_mod_visible_fn();
}
}
fn bar() {
// 동일한 크레이트에 있으므로 이 함수는 여전히 보입니다.
outer_mod::inner_mod::crate_visible_fn();
// `outer_mod` 외부에 있으므로 이 함수는 더 이상 보이지 않습니다.
// 오류! `super_mod_visible_fn` 은 비공개입니다.
//outer_mod::inner_mod::super_mod_visible_fn();
// `outer_mod` 외부에 있으므로 이 함수는 더 이상 보이지 않습니다.
// 오류! `outer_mod_visible_fn` 은 비공개입니다.
//outer_mod::inner_mod::outer_mod_visible_fn();
outer_mod::foo();
}
fn main() { bar() }
Note
This syntax only adds another restriction to the visibility of an item. It does not guarantee that the item is visible within all parts of the specified scope. To access an item, all of its parent items up to the current scope must still be visible as well.
Re-exporting and visibility
러스트는 pub use 지시어를 통해 아이템을 공개적으로 재내보낼 수 있게 해줍니다. 이것은 공개 지시어이므로, 위의 규칙에 따라 현재 모듈에서 해당 아이템을 사용할 수 있게 됩니다. 본질적으로 재내보낸 아이템에 대한 공개 접근을 허용하는 것입니다. 예를 들어, 다음 프로그램은 유효합니다:
pub use self::implementation::api;
mod implementation {
pub mod api {
pub fn f() {}
}
}
fn main() {}
즉, implementation::api::f 를 참조하는 외부 크레이트는 프라이버시 위반 오류를 받게 되지만, api::f 경로는 허용됩니다.
비공개 아이템을 재내보낼 때, 이는 “프라이버시 체인“이 일반적인 네임스페이스 계층 구조를 거치지 않고 재내보내기를 통해 단락(short-circuited)되는 것을 허용하는 것으로 생각할 수 있습니다.
메모리 모델
Warning
The memory model of Rust is incomplete and not fully decided.
Bytes
The most basic unit of memory in Rust is a byte.
Note
While bytes are typically lowered to hardware bytes, Rust uses an “abstract” notion of bytes that can make distinctions which are absent in hardware, such as being uninitialized, or storing part of a pointer. Those distinctions can affect whether your program has undefined behavior, so they still have tangible impact on how compiled Rust programs behave.
Each byte may have one of the following values:
- An initialized byte containing a
u8value and optional provenance,
- An uninitialized byte.
Note
The above list is not yet guaranteed to be exhaustive.
메모리 할당과 라이프타임
프로그램의 아이템(items) 은 컴파일 시간에 값이 계산되고 러스트 프로세스의 메모리 이미지에 고유하게 저장되는 함수, 모듈, 타입들입니다. 아이템은 동적으로 할당되거나 해제되지 않습니다.
힙(heap) 은 박스(boxes)를 설명하는 일반적인 용어입니다. 힙 할당의 수명은 이를 가리키는 박스 값의 수명에 따라 달라집니다. 박스 값 자체가 스택 프레임(frame) 내외부로 전달되거나 힙에 저장될 수 있으므로, 힙 할당은 할당된 프레임보다 더 오래 지속될 수 있습니다. 힙 할당은 할당된 전체 수명 동안 힙의 단일 위치에 머무르는 것이 보장됩니다. 즉, 박스 값을 이동하더라도 재배치되지 않습니다.
변수
변수(variable) 는 스택 프레임의 구성 요소로, 이름을 가진 함수 파라미터, 익명 임시 값(temporary), 또는 이름을 가진 지역 변수입니다.
지역 변수(local variable)(또는 스택-로컬 할당)는 스택 메모리 내에 할당되어 값을 직접 보관합니다. 이 값은 스택 프레임의 일부입니다.
지역 변수는 별도로 선언하지 않는 한 불변(immutable)입니다. 예: let mut x = ....
함수 파라미터는 mut 로 선언되지 않는 한 불변입니다. mut 키워드는 바로 다음에 오는 파라미터에만 적용됩니다. 예를 들어, |mut x, y| 와 fn f(mut x: Box<i32>, y: Box<i32>) 는 가변 변수 x 하나와 불변 변수 y 하나를 선언합니다.
지역 변수는 할당될 때 초기화되지 않습니다. 대신, 프레임에 진입할 때 프레임에 해당하는 전체 지역 변수가 초기화되지 않은 상태로 할당됩니다. 함수 내의 후속 구문들이 지역 변수를 초기화할 수도 있고 그렇지 않을 수도 있습니다. 지역 변수는 모든 도달 가능한 제어 흐름 경로를 통해 초기화된 후에만 사용할 수 있습니다.
다음 예제에서 init_after_if 는 if 표현식 이후에 초기화되지만, uninit_after_if 는 else 케이스에서 초기화되지 않으므로 초기화되지 않은 상태로 남습니다.
#![allow(unused)]
fn main() {
fn random_bool() -> bool { true }
fn initialization_example() {
let init_after_if: ();
let uninit_after_if: ();
if random_bool() {
init_after_if = ();
uninit_after_if = ();
} else {
init_after_if = ();
}
init_after_if; // ok
// uninit_after_if; // err: 초기화되지 않았을 가능성이 있는 `uninit_after_if` 사용
}
}
Panic
Rust provides a mechanism to prevent a function from returning normally, and instead “panic,” which is a response to an error condition that is typically not expected to be recoverable within the context in which the error is encountered.
Some language constructs, such as out-of-bounds array indexing, panic automatically.
There are also language features that provide a level of control over panic behavior:
- A panic handler defines the behavior of a panic.
- FFI ABIs may alter how panics behave.
Note
The standard library provides the capability to explicitly panic via the
panic!macro.
panic_handler 속성
The panic_handler attribute can be applied to a function to define the behavior of panics.
The panic_handler attribute can only be applied to a function with signature fn(&PanicInfo) -> !.
Note
PanicInfo구조체는 패닉이 발생한 위치에 대한 정보를 포함합니다.
There must be a single panic_handler function in the dependency graph.
아래는 패닉 메시지를 기록한 다음 스레드를 중단시키는 panic_handler 함수의 예시입니다.
#![no_std]
use core::fmt::{self, Write};
use core::panic::PanicInfo;
struct Sink {
// ..
_0: (),
}
impl Sink {
fn new() -> Sink { Sink { _0: () }}
}
impl fmt::Write for Sink {
fn write_str(&mut self, _: &str) -> fmt::Result { Ok(()) }
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
let mut sink = Sink::new();
// "panicked at '$reason', src/main.rs:27:4"를 어떤 `sink` 에 기록함
let _ = writeln!(sink, "{}", info);
loop {}
}
표준 동작
std provides two different panic handlers:
unwind— unwinds the stack and is potentially recoverable.abort–– aborts the process and is non-recoverable.
Not all targets may provide the unwind handler.
Note
The panic handler used when linking with
stdcan be set with the-C panicCLI flag. The default for most targets isunwind.The standard library’s panic behavior can be modified at runtime with the
std::panic::set_hookfunction.
Linking a no_std binary, dylib, cdylib, or staticlib will require specifying your own panic handler.
Panic strategy
The panic strategy defines the kind of panic behavior that a crate is built to support.
Note
The panic strategy can be chosen in
rustcwith the-C panicCLI flag.When generating a binary, dylib, cdylib, or staticlib and linking with
std, the-C panicCLI flag also influences which panic handler is used.
Note
When compiling code with the
abortpanic strategy, the optimizer may assume that unwinding across Rust frames is impossible, which can result in both code-size and runtime speed improvements.
Note
See link.unwinding for restrictions on linking crates with different panic strategies. An implication is that crates built with the
unwindstrategy can use theabortpanic handler, but theabortstrategy cannot use theunwindpanic handler.
Unwinding
Panicking may either be recoverable or non-recoverable, though it can be configured (by choosing a non-unwinding panic handler) to always be non-recoverable. (The converse is not true: the unwind handler does not guarantee that all panics are recoverable, only that panicking via the panic! macro and similar standard library mechanisms is recoverable.)
When a panic occurs, the unwind handler “unwinds” Rust frames, just as C++’s throw unwinds C++ frames, until the panic reaches the point of recovery (for instance at a thread boundary). This means that as the panic traverses Rust frames, live objects in those frames that implement Drop will have their drop methods called. Thus, when normal execution resumes, no-longer-accessible objects will have been “cleaned up” just as if they had gone out of scope normally.
Note
As long as this guarantee of resource-cleanup is preserved, “unwinding” may be implemented without actually using the mechanism used by C++ for the target platform.
Note
The standard library provides two mechanisms for recovering from a panic,
std::panic::catch_unwind(which enables recovery within the panicking thread) andstd::thread::spawn(which automatically sets up panic recovery for the spawned thread so that other threads may continue running).
Unwinding across FFI boundaries
It is possible to unwind across FFI boundaries using an appropriate ABI declaration. While useful in certain cases, this creates unique opportunities for undefined behavior, especially when multiple language runtimes are involved.
Unwinding with the wrong ABI is undefined behavior:
- Causing an unwind into Rust code from a foreign function that was called via a function declaration or pointer declared with a non-unwinding ABI, such as
"C","system", etc. (For example, this case occurs when such a function written in C++ throws an exception that is uncaught and propagates to Rust.) - Calling a Rust
externfunction that unwinds (withextern "C-unwind"or another ABI that permits unwinding) from code that does not support unwinding, such as code compiled with GCC or Clang using-fno-exceptions
Catching a foreign unwinding operation (such as a C++ exception) using std::panic::catch_unwind, std::thread::JoinHandle::join, or by letting it propagate beyond the Rust main() function or thread root will have one of two behaviors, and it is unspecified which will occur:
- The process aborts.
- The function returns a
Result::Errcontaining an opaque type.
Note
Rust code compiled or linked with a different instance of the Rust standard library counts as a “foreign exception” for the purpose of this guarantee. Thus, a library that uses
panic!and is linked against one version of the Rust standard library, invoked from an application that uses a different version of the standard library, may cause the entire application to abort even if the library is only used within a child thread.
There are currently no guarantees about the behavior that occurs when a foreign runtime attempts to dispose of, or rethrow, a Rust panic payload. In other words, an unwind originated from a Rust runtime must either lead to termination of the process or be caught by the same runtime.
연결
Note
This section is described more in terms of the compiler than of the language.
The compiler supports various methods to link crates together both statically and dynamically. This section will explore the various methods to link crates together, and more information about native libraries can be found in the FFI section of the book.
한 번의 컴파일 세션에서 컴파일러는 커맨드 라인 플래그나 crate_type 속성을 사용하여 여러 결과물(artifacts)을 생성할 수 있습니다. 하나 이상의 커맨드 라인 플래그가 지정되면 모든 crate_type 속성은 무시되고 커맨드 라인에서 지정된 결과물만 빌드됩니다.
--crate-type=bin,#![crate_type = "bin"]- 실행 가능한 프로그램이 생성됩니다. 이 경우 프로그램이 실행될 때 호출될main함수가 크레이트 내에 반드시 있어야 합니다. 모든 러스트 및 네이티브 의존성을 링크하여 배포 가능한 단일 바이너리를 생성합니다. 이것이 기본 크레이트 타입입니다.
--crate-type=lib,#![crate_type = "lib"]- 러스트 라이브러리가 생성됩니다. 라이브러리는 여러 형태로 나타날 수 있기 때문에 무엇이 정확히 생성되는지는 모호할 수 있습니다. 이 일반적인lib옵션의 목적은 “컴파일러가 권장하는” 스타일의 라이브러리를 생성하는 것입니다. 생성된 라이브러리는 항상 rustc에서 사용할 수 있지만, 실제 라이브러리 타입은 수시로 바뀔 수 있습니다. 나머지 출력 타입들은 모두 서로 다른 종류의 라이브러리들이며,lib타입은 그 중 하나에 대한 별칭으로 볼 수 있습니다(실제 타입은 컴파일러가 정의합니다).
--crate-type=dylib,#![crate_type = "dylib"]- 동적 러스트 라이브러리가 생성됩니다. 이는lib출력 타입과 달리 동적 라이브러리 생성을 강제합니다. 생성된 동적 라이브러리는 다른 라이브러리나 실행 파일의 의존성으로 사용될 수 있습니다. 이 출력 타입은 리눅스에서는*.so, macOS에서는*.dylib, 윈도우에서는*.dll파일을 생성합니다.
-
--crate-type=staticlib,#![crate_type = "staticlib"]- 정적 시스템 라이브러리가 생성됩니다. 이는 다른 라이브러리 출력과 달리, 컴파일러가staticlib출력에 링크하려고 시도하지 않습니다. 이 출력 타입의 목적은 로컬 크레이트의 모든 코드와 모든 업스트림 의존성을 포함하는 정적 라이브러리를 만드는 것입니다. 이 출력 타입은 리눅스, macOS 및 윈도우(MinGW)에서는*.a파일을, 윈도우(MSVC)에서는*.lib파일을 생성합니다. 이 형식은 다른 러스트 코드에 대한 동적 의존성이 없기 때문에, 기존의 비-러스트 애플리케이션에 러스트 코드를 링크하는 것과 같은 상황에서 사용하는 것을 권장합니다.정적 라이브러리가 가질 수 있는 모든 동적 의존성(예: 시스템 라이브러리에 대한 의존성, 또는 동적 라이브러리로 컴파일된 러스트 라이브러리에 대한 의존성)은 해당 정적 라이브러리를 어딘가에서 링크할 때 수동으로 지정해야 함에 유의하십시오.
--print=native-static-libs플래그가 이 작업에 도움이 될 수 있습니다.생성된 정적 라이브러리는 표준 라이브러리를 포함한 모든 의존성의 코드를 포함하고 이들의 모든 공개 심볼을 내보내기 때문에, 정적 라이브러리를 실행 파일이나 공유 라이브러리에 링크할 때는 특별한 주의가 필요할 수 있음에 유의하십시오. 공유 라이브러리의 경우, 내보낼 심볼 목록을 링커나 심볼 버전 스크립트, 내보낸 심볼 목록(macOS) 또는 모듈 정의 파일(윈도우) 등을 통해 제한해야 합니다. 또한, 실제로 사용되지 않는 의존성 코드를 모두 제거하기 위해 사용되지 않는 섹션을 제거할 수 있습니다 (예:
--gc-sections또는 macOS의-dead_strip).
--crate-type=cdylib,#![crate_type = "cdylib"]- 동적 시스템 라이브러리가 생성됩니다. 이는 다른 언어에서 로드할 동적 라이브러리를 컴파일할 때 사용됩니다. 이 출력 타입은 리눅스에서는*.so, macOS에서는*.dylib, 윈도우에서는*.dll파일을 생성합니다.
--crate-type=rlib,#![crate_type = "rlib"]- “러스트 라이브러리” 파일이 생성됩니다. 이는 중간 생성물로 사용되며 “정적 러스트 라이브러리“라고 생각할 수 있습니다. 이러한rlib파일은staticlib파일과 달리 향후 링크 시 컴파일러에 의해 해석됩니다. 이는 본질적으로rustc가 동적 라이브러리에서 메타데이터를 찾는 것처럼rlib파일에서도 메타데이터를 찾는다는 것을 의미합니다. 이 출력 형식은 정적으로 링크된 실행 파일과staticlib출력을 생성하는 데 사용됩니다.
--crate-type=proc-macro,#![crate_type = "proc-macro"]- 생성되는 출력은 명시되지 않았지만,-L경로가 제공되면 컴파일러는 출력 결과물을 매크로로 인식하고 프로그램에 로드할 수 있습니다. 이 크레이트 타입으로 컴파일된 크레이트는 반드시 절차적 매크로 만 내보내야 합니다. 컴파일러는 자동으로proc_macro설정 옵션 을 설정합니다. 크레이트는 항상 컴파일러 자체가 빌드된 것과 동일한 타겟으로 컴파일됩니다. 예를 들어, 리눅스x86_64CPU에서 컴파일러를 실행 중이라면, 다른 타겟을 위해 빌드 중인 다른 크레이트의 의존성일지라도 타겟은x86_64-unknown-linux-gnu가 됩니다.
이러한 출력들은 여러 개가 지정된 경우 컴파일러가 재컴파일 없이 각 형식의 출력을 생성한다는 의미에서 중첩 가능함에 유의하십시오. 하지만 이는 동일한 방법으로 지정된 출력에만 적용됩니다. crate_type 속성만 지정된 경우 모두 빌드되지만, 하나 이상의 --crate-type 커맨드 라인 플래그가 지정된 경우 해당 출력들만 빌드됩니다.
이러한 다양한 종류의 출력들과 함께, 만약 크레이트 A가 크레이트 B에 의존한다면 컴파일러는 시스템 전체에서 B를 여러 가지 다른 형식으로 찾을 수 있습니다. 하지만 컴파일러가 찾는 유일한 형식은 rlib 형식과 동적 라이브러리 형식입니다. 의존 라이브러리에 대한 이 두 가지 옵션 중에서 컴파일러는 어느 시점에 이 두 형식 중 하나를 선택해야 합니다. 이를 염두에 두고, 컴파일러는 어떤 형식의 의존성을 사용할지 결정할 때 다음 규칙을 따릅니다.
-
정적 라이브러리가 생성되는 경우, 모든 업스트림 의존성은
rlib형식으로 사용 가능해야 합니다. 이 요구 사항은 동적 라이브러리를 정적 형식으로 변환할 수 없다는 이유에서 비롯됩니다.정적 라이브러리에 네이티브 동적 의존성을 링크하는 것은 불가능하며, 이 경우 링크되지 않은 모든 네이티브 동적 의존성에 대한 경고가 출력됨에 유의하십시오.
-
rlib파일이 생성되는 경우, 업스트림 의존성이 어떤 형식으로 제공되는지에 대한 제한은 없습니다. 단지 메타데이터를 읽기 위해 모든 업스트림 의존성이 사용 가능해야 합니다.그 이유는
rlib파일이 업스트림 의존성을 전혀 포함하지 않기 때문입니다. 모든rlib파일이libstd.rlib사본을 포함한다면 그다지 효율적이지 않을 것입니다!
- 실행 파일이 생성되고
-C prefer-dynamic플래그가 지정되지 않은 경우, 먼저rlib형식의 의존성을 찾으려고 시도합니다. 일부 의존성을 rlib 형식으로 사용할 수 없는 경우, 동적 링크가 시도됩니다(아래 참조).
-
동적 라이브러리나 동적으로 링크되는 실행 파일이 생성되는 경우, 컴파일러는 최종 결과물을 만들기 위해 사용 가능한 의존성들을 rlib 또는 dylib 형식 중에서 조정하려고 시도합니다.
컴파일러의 주요 목표는 라이브러리가 어떤 결과물에서도 두 번 이상 나타나지 않도록 보장하는 것입니다. 예를 들어, 만약 동적 라이브러리 B와 C가 각각 라이브러리 A에 정적으로 링크되어 있다면, A의 사본이 두 개가 되기 때문에 어떤 크레이트도 B와 C를 동시에 링크할 수 없습니다. 컴파일러는 rlib와 dylib 형식의 혼합을 허용하지만, 이 제한 사항은 반드시 준수되어야 합니다.
컴파일러는 현재 라이브러리를 어떤 형식으로 링크해야 하는지에 대한 힌트를 제공하는 방법을 구현하고 있지 않습니다. 동적 링크 시 컴파일러는 일부 의존성을 rlib를 통해 링크하는 것을 허용하면서도 동적 의존성을 최대화하려고 시도합니다.
대부분의 상황에서, 동적 링크를 하는 경우 모든 라이브러리를 dylib로 사용할 수 있도록 하는 것이 권장됩니다. 다른 상황에서 컴파일러는 각 라이브러리를 어떤 형식으로 링크해야 할지 결정할 수 없는 경우 경고를 출력합니다.
일반적으로 모든 컴파일 요구 사항에 대해 --crate-type=bin 또는 --crate-type=lib 로 충분하며, 다른 옵션들은 크레이트의 출력 형식에 대해 더 세밀한 제어가 필요한 경우에만 사용 가능합니다.
정적 및 동적 C 런타임
표준 라이브러리는 일반적으로 타겟에 적절하게 정적 링크 및 동적 링크 C 런타임을 모두 지원하려고 노력합니다. 예를 들어 x86_64-pc-windows-msvc 및 x86_64-unknown-linux-musl 타겟은 일반적으로 두 런타임을 모두 제공하며 사용자가 원하는 것을 선택합니다. 컴파일러의 모든 타겟은 C 런타임에 링크하는 기본 모드를 가지고 있습니다. 일반적으로 타겟은 기본적으로 동적으로 링크되지만, 다음과 같이 기본적으로 정적인 예외도 있습니다.
arm-unknown-linux-musleabiarm-unknown-linux-musleabihfarmv7-unknown-linux-musleabihfi686-unknown-linux-muslx86_64-unknown-linux-musl
C 런타임의 링크는 crt-static 타겟 기능을 준수하도록 설정됩니다. 이러한 타겟 기능은 일반적으로 컴파일러 자체에 대한 플래그를 통해 커맨드 라인에서 설정됩니다. 예를 들어 정적 런타임을 활성화하려면 다음과 같이 실행합니다.
rustc -C target-feature=+crt-static foo.rs
반면 C 런타임에 동적으로 링크하려면 다음과 같이 실행합니다.
rustc -C target-feature=-crt-static foo.rs
C 런타임 링크 방식의 전환을 지원하지 않는 타겟은 이 플래그를 무시합니다. 컴파일러가 성공적으로 종료된 후 결과 바이너리를 검사하여 예상대로 링크되었는지 확인하는 것이 좋습니다.
크레이트 또한 C 런타임이 어떻게 링크되었는지 알 수 있습니다. 예를 들어, MSVC에서의 코드는 링크되는 런타임에 따라 다르게 컴파일되어야 합니다(예: /MT 또는 /MD). 이 정보는 현재 cfg 속성의 target_feature 옵션 을 통해 제공됩니다:
#![allow(unused)]
fn main() {
#[cfg(target_feature = "crt-static")]
fn foo() {
println!("C 런타임은 정적으로 링크되어야 함");
}
#[cfg(not(target_feature = "crt-static"))]
fn foo() {
println!("C 런타임은 동적으로 링크되어야 함");
}
}
또한 Cargo 빌드 스크립트는 환경 변수 를 통해 이 기능에 대해 알 수 있습니다. 빌드 스크립트에서는 다음과 같이 링크 여부를 감지할 수 있습니다:
use std::env;
fn main() {
let linkage = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or(String::new());
if linkage.contains("crt-static") {
println!("C 런타임은 정적으로 링크될 것임");
} else {
println!("C 런타임은 동적으로 링크될 것임");
}
}
로컬에서 이 기능을 사용하려면 일반적으로 RUSTFLAGS 환경 변수를 사용하여 Cargo를 통해 컴파일러 플래그를 지정합니다. 예를 들어 MSVC에서 정적으로 링크된 바이너리를 컴파일하려면 다음과 같이 실행합니다:
RUSTFLAGS='-C target-feature=+crt-static' cargo build --target x86_64-pc-windows-msvc
러스트와 외부 코드베이스의 혼합
러스트를 외부 코드(예: C, C++)와 혼합하여 두 종류의 코드를 모두 포함하는 단일 바이너리를 만들고자 한다면, 최종 바이너리 링크를 위해 두 가지 접근 방식이 있습니다.
rustc를 사용합니다. 비-러스트 라이브러리는rustc인자인-L <directory>및-l<library>를 사용하거나 러스트 코드의#[link]지시어를 통해 전달합니다..o파일에 링크해야 한다면-Clink-arg=file.o를 사용할 수 있습니다.- 외부 링커를 사용합니다. 이 경우, 먼저 러스트
staticlib타겟을 생성하고 이를 외부 링커 호출 시 전달해야 합니다. 여러 개의 러스트 서브시스템을 링크해야 한다면, 여러 개의extern crate문을 사용하여 여러 러스트rlib를 포함하는 단일staticlib를 생성해야 할 수도 있습니다. 여러 개의 러스트staticlib파일은 충돌할 가능성이 높습니다.
러스트 rlib 를 외부 링커에 직접 전달하는 것은 현재 지원되지 않습니다.
Note
Rust code compiled or linked with a different instance of the Rust runtime counts as “foreign code” for the purpose of this section.
Prohibited linkage and unwinding
Panic unwinding can only be used if the binary is built consistently according to the following rules.
A Rust artifact is called potentially unwinding if any of the following conditions is met:
- The artifact uses the
unwindpanic handler. - The artifact contains a crate built with the
unwindpanic strategy that makes a call to a function using a-unwindABI. - The artifact makes a
"Rust"ABI call to code running in another Rust artifact that has a separate copy of the Rust runtime, and that other artifact is potentially unwinding.
Note
This definition captures whether a
"Rust"ABI call inside a Rust artifact can ever unwind.
If a Rust artifact is potentially unwinding, then all its crates must be built with the unwind panic strategy. Otherwise, unwinding can cause undefined behavior.
Note
If you are using
rustcto link, these rules are enforced automatically. If you are not usingrustcto link, you must take care to ensure that unwinding is handled consistently across the entire binary. Linking withoutrustcincludes usingdlopenor similar facilities where linking is done by the system runtime withoutrustcbeing involved. This can only happen when mixing code with different-C panicflags, so most users do not have to be concerned about this.
Note
To guarantee that a library will be sound (and linkable with
rustc) regardless of the panic runtime used at link-time, theffi_unwind_callslint may be used. The lint flags any calls to-unwindforeign functions or function pointers.
인라인 어셈블리
Support for inline assembly is provided via the asm!, naked_asm!, and global_asm! macros. It can be used to embed handwritten assembly in the assembly output generated by the compiler.
인라인 어셈블리 지원은 다음 아키텍처에서 안정화되었습니다:
- x86 및 x86-64
- ARM
- AArch64 및 Arm64EC
- RISC-V
- LoongArch
- s390x
- PowerPC and PowerPC64
The compiler will emit an error if an assembly macro is used on an unsupported target.
예시
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
use std::arch::asm;
// 시프트와 덧셈을 사용하여 x에 6을 곱합니다.
let mut x: u64 = 4;
unsafe {
asm!(
"mov {tmp}, {x}",
"shl {tmp}, 1",
"shl {x}, 2",
"add {x}, {tmp}",
x = inout(reg) x,
tmp = out(reg) _,
);
}
assert_eq!(x, 4 * 6);
}
}
구문
The following grammar specifies the arguments that can be passed to the asm!, global_asm! and naked_asm! macros.
Syntax
AsmArgs → AsmAttrFormatString ( , AsmAttrFormatString )* ( , AsmAttrOperand )* ,?
FormatString → STRING_LITERAL | RAW_STRING_LITERAL | MacroInvocation
AsmAttrFormatString → ( OuterAttribute )* FormatString
AsmOperand →
ClobberAbi
| AsmOptions
| RegOperand
AsmAttrOperand → ( OuterAttribute )* AsmOperand
ClobberAbi → clobber_abi ( Abi ( , Abi )* ,? )
AsmOptions →
options ( ( AsmOption ( , AsmOption )* ,? )? )
AsmOption →
pure
| nomem
| readonly
| preserves_flags
| noreturn
| nostack
| att_syntax
| raw
RegOperand → ( ParamName = )?
(
DirSpec ( RegSpec ) Expression
| DualDirSpec ( RegSpec ) DualDirSpecExpression
| sym PathExpression
| const Expression
| label { Statements? }
)
ParamName → IDENTIFIER_OR_KEYWORD | RAW_IDENTIFIER
DualDirSpecExpression →
Expression
| Expression => Expression
RegSpec → RegisterClass | ExplicitRegister
RegisterClass → IDENTIFIER_OR_KEYWORD
ExplicitRegister → STRING_LITERAL
DirSpec →
in
| out
| lateout
DualDirSpec →
inout
| inlateout
스코프
Inline assembly can be used in one of three ways.
asm! 매크로를 사용하면, 어셈블리 코드는 함수 스코프에서 배출되어 컴파일러가 생성한 함수의 어셈블리 코드와 통합됩니다. 이 어셈블리 코드는 정의되지 않은 동작을 피하기 위해 엄격한 규칙 을 따라야 합니다. 경우에 따라 컴파일러는 어셈블리 코드를 별도의 함수로 배출하고 그 함수에 대한 호출을 생성할 수도 있습니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
unsafe { core::arch::asm!("/* {} */", in(reg) 0); }
}
}
With the naked_asm! macro, the assembly code is emitted in a function scope and constitutes the full assembly code of a function. The naked_asm! macro is only allowed in naked functions.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
#[unsafe(naked)]
extern "C" fn wrapper() {
core::arch::naked_asm!("/* {} */", const 0);
}
}
}
With the global_asm! macro, the assembly code is emitted in a global scope, outside a function. This can be used to hand-write entire functions using assembly code, and generally provides much more freedom to use arbitrary registers and assembler directives.
fn main() {}
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!("/* {} */", const 0);
템플릿 문자열 인자
어셈블러 템플릿은 포맷 문자열 과 동일한 구문을 사용합니다 (즉, 플레이스홀더는 중괄호로 지정됩니다).
해당 인자들은 순서대로, 인덱스로, 또는 이름으로 접근됩니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64;
let y: i64;
let z: i64;
// 이것과
unsafe { core::arch::asm!("mov {}, {}", out(reg) x, in(reg) 5); }
// ... 이것
unsafe { core::arch::asm!("mov {0}, {1}", out(reg) y, in(reg) 5); }
// ... 그리고 이것은
unsafe { core::arch::asm!("mov {out}, {in}", out = out(reg) z, in = in(reg) 5); }
// 모두 동일하게 동작합니다.
assert_eq!(x, y);
assert_eq!(y, z);
}
}
하지만, (RFC #2795 에서 도입된) 암시적 명명된 인자(implicit named arguments)는 지원되지 않습니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x = 5;
// 스코프에서 직접 `x` 를 참조할 수 없으며, `in(reg) x` 와 같은 피연산자가 필요합니다.
unsafe { core::arch::asm!("/* {x} */"); } // 오류: x라는 이름의 인자가 없습니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
asm! 호출은 하나 이상의 템플릿 문자열 인자를 가질 수 있습니다. 여러 개의 템플릿 문자열 인자가 있는 asm! 은 모든 문자열이 사이에 \n 을 두고 연결된 것처럼 처리됩니다. 권장되는 사용법은 각 템플릿 문자열 인자가 어셈블리 코드의 한 줄에 대응하도록 하는 것입니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64;
let y: i64;
// 여러 문자열을 마치 함께 작성된 것처럼 분리할 수 있습니다.
unsafe { core::arch::asm!("mov eax, 5", "mov ecx, eax", out("rax") x, out("rcx") y); }
assert_eq!(x, y);
}
}
모든 템플릿 문자열 인자는 다른 인자들보다 먼저 나타나야 합니다.
#![allow(unused)]
fn main() {
let x = 5;
#[cfg(target_arch = "x86_64")] {
// 템플릿 문자열은 asm 호출에서 가장 먼저 나타나야 합니다.
unsafe { core::arch::asm!("/* {x} */", x = const 5, "ud2"); } // 오류: 예상치 못한 토큰
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
포맷 문자열과 마찬가지로, 위치 기반 인자(positional arguments)는 명명된 인자나 명시적 레지스터 피연산자 보다 먼저 나타나야 합니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// 명명된 피연산자는 위치 기반 피연산자 뒤에 와야 합니다.
unsafe { core::arch::asm!("/* {x} {} */", x = const 5, in(reg) 5); }
// 오류: 위치 기반 인자는 명명된 인자나 명시적 레지스터 인자 뒤에 올 수 없습니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// 또한 위치 기반 피연산자 앞에 명시적 레지스터를 둘 수 없습니다.
unsafe { core::arch::asm!("/* {} */", in("eax") 0, in(reg) 5); }
// 오류: 위치 기반 인자는 명명된 인자나 명시적 레지스터 인자 뒤에 올 수 없습니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
명시적 레지스터 피연산자는 템플릿 문자열의 플레이스홀더에서 사용될 수 없습니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// 명시적 레지스터 피연산자는 치환되지 않으므로, 문자열에서 `eax` 를 명시적으로 사용하십시오.
unsafe { core::arch::asm!("/* {} */", in("eax") 5); }
// 오류: 인덱스 0의 인자에 대한 잘못된 참조
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
다른 모든 명명된 및 위치 기반 피연산자는 템플릿 문자열에 최소 한 번 이상 나타나야 하며, 그렇지 않으면 컴파일러 오류가 발생합니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// 포맷 문자열에서 모든 피연산자의 이름을 지정해야 합니다.
unsafe { core::arch::asm!("", in(reg) 5, x = const 5); }
// 오류: 사용되지 않은 여러 개의 asm 인자가 있습니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
정확한 어셈블리 코드 구문은 타겟에 따라 다르며, 피연산자가 템플릿 문자열에 치환되어 어셈블러에 전달될 코드를 형성하는 방식을 제외하고는 컴파일러에게 불투명(opaque)합니다.
현재 모든 지원되는 타겟은 LLVM 내부 어셈블러에서 사용하는 어셈블리 코드 구문을 따르며, 이는 대개 GNU 어셈블러(GAS)의 구문과 일치합니다. x86에서는 기본적으로 GAS의 .intel_syntax noprefix 모드가 사용됩니다. ARM에서는 .syntax unified 모드가 사용됩니다. 이러한 타겟들은 어셈블리 코드에 추가적인 제약을 가합니다. 즉, 모든 어셈블러 상태(예: .section 으로 변경될 수 있는 현재 섹션)는 asm 문자열의 끝에서 원래 값으로 복구되어야 합니다. GAS 구문을 따르지 않는 어셈블리 코드는 어셈블러에 따라 다른 동작을 유발할 수 있습니다. 인라인 어셈블리에서 사용되는 지시어(directives)에 대한 추가적인 제약 조건은 지시어 지원 섹션에 명시되어 있습니다.
속성
Only the cfg and cfg_attr attributes are accepted semantically on inline assembly template strings and operands. Other attributes are parsed but rejected when the assembly macro is expanded.
fn main() {}
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!(
#[cfg(not(panic = "abort"))]
".cfi_startproc",
// ...
"ret",
#[cfg(not(panic = "abort"))]
".cfi_endproc",
);
Note
In
rustc, the assembly macros implement handling of these attributes separately from the normal system that handles similar attributes in the language. This accounts for the limited kinds of attributes supported and may give rise to subtle differences in behavior.
Syntactically there must be at least one template string before the first operand.
#![allow(unused)]
fn main() {
// This is rejected because `a = out(reg) x` does not parse as a
// template string.
core::arch::asm!(
#[cfg(false)]
a = out(reg) x, // ERROR.
"",
);
}
피연산자 타입
여러 종류의 피연산자가 지원됩니다:
in(<reg>) <expr><reg>는 레지스터 클래스 또는 명시적 레지스터를 참조할 수 있습니다. 할당된 레지스터 이름이 asm 템플릿 문자열에 치환됩니다.- The allocated register will contain the value of
<expr>at the start of the assembly code. - The allocated register must contain the same value at the end of the assembly code (except if a
lateoutis allocated to the same register).
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// `in` 은 인라인 어셈블리에 값을 전달하는 데 사용될 수 있습니다...
unsafe { core::arch::asm!("/* {} */", in(reg) 5); }
}
}
out(<reg>) <expr><reg>는 레지스터 클래스 또는 명시적 레지스터를 참조할 수 있습니다. 할당된 레지스터 이름이 asm 템플릿 문자열에 치환됩니다.- The allocated register will contain an undefined value at the start of the assembly code.
<expr>must be a (possibly uninitialized) place expression, to which the contents of the allocated register are written at the end of the assembly code.- An underscore (
_) may be specified instead of an expression, which will cause the contents of the register to be discarded at the end of the assembly code (effectively acting as a clobber).
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64;
// 그리고 `out` 은 값을 다시 러스트로 전달하는 데 사용될 수 있습니다.
unsafe { core::arch::asm!("/* {} */", out(reg) x); }
}
}
lateout(<reg>) <expr>out과 동일하지만, 레지스터 할당기가in에 할당된 레지스터를 재사용할 수 있습니다.- 모든 입력이 읽힌 후에만 레지스터에 기록해야 합니다. 그렇지 않으면 입력을 덮어쓸(clobber) 수 있습니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64;
// `lateout` 은 `out` 과 동일하지만,
// 우리가 값을 덮어쓸 시점에 입력값들에 대해 더 이상 신경 쓰지 않는다는 것을
// 컴파일러가 알고 있습니다.
unsafe { core::arch::asm!("mov {}, 5", lateout(reg) x); }
assert_eq!(x, 5)
}
}
inout(<reg>) <expr><reg>는 레지스터 클래스 또는 명시적 레지스터를 참조할 수 있습니다. 할당된 레지스터 이름이 asm 템플릿 문자열에 치환됩니다.- The allocated register will contain the value of
<expr>at the start of the assembly code. <expr>must be a mutable initialized place expression, to which the contents of the allocated register are written at the end of the assembly code.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x: i64 = 4;
// `inout` 은 레지스터 내에서 값을 수정하는 데 사용될 수 있습니다.
unsafe { core::arch::asm!("inc {}", inout(reg) x); }
assert_eq!(x, 5);
}
}
inout(<reg>) <in expr> => <out expr>inout과 동일하지만, 레지스터의 초기값이<in expr>의 값에서 취해집니다.<out expr>must be a (possibly uninitialized) place expression, to which the contents of the allocated register are written at the end of the assembly code.- An underscore (
_) may be specified instead of an expression for<out expr>, which will cause the contents of the register to be discarded at the end of the assembly code (effectively acting as a clobber). <in expr>과<out expr>은 서로 다른 타입을 가질 수 있습니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64;
// `inout` 은 또한 값을 다른 장소로 이동시킬 수 있습니다.
unsafe { core::arch::asm!("inc {}", inout(reg) 4u64=>x); }
assert_eq!(x, 5);
}
}
inlateout(<reg>) <expr>/inlateout(<reg>) <in expr> => <out expr>inout과 동일하지만, 레지스터 할당기가in에 할당된 레지스터를 재사용할 수 있습니다(이는 컴파일러가in이inlateout과 동일한 초기값을 갖는다는 것을 아는 경우 발생할 수 있습니다).- 모든 입력이 읽힌 후에만 레지스터에 기록해야 합니다. 그렇지 않으면 입력을 덮어쓸(clobber) 수 있습니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x: i64 = 4;
// `inlateout` 은 `lateout` 을 사용하는 `inout` 입니다.
unsafe { core::arch::asm!("inc {}", inlateout(reg) x); }
assert_eq!(x, 5);
}
}
sym <path><path>는fn또는static을 참조해야 합니다.- 해당 아이템을 참조하는 맹글링된(mangled) 심볼 이름이 asm 템플릿 문자열에 치환됩니다.
- 치환된 문자열에는 어떠한 수식어(예: GOT, PLT, 재배치(relocations) 등)도 포함되지 않습니다.
<path>is allowed to point to a#[thread_local]static, in which case the assembly code can combine the symbol with relocations (e.g.@plt,@TPOFF) to read from thread-local data.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
extern "C" fn foo() {
println!("Hello from inline assembly")
}
// `sym` 은 (직접 쓸 수 있는 외부 이름이 없는 경우에도) 함수를 참조하는 데 사용될 수 있습니다.
unsafe { core::arch::asm!("call {}", sym foo, clobber_abi("C")); }
}
}
const <expr><expr>은 정수 상수 표현식이어야 합니다. 이 표현식은 인라인const블록과 동일한 규칙을 따릅니다.- 표현식의 타입은 모든 정수 타입이 될 수 있지만, 정수 리터럴과 마찬가지로 기본값은
i32입니다. - 표현식의 값은 문자열로 포맷되어 asm 템플릿 문자열에 직접 치환됩니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// swizzle [0, 1, 2, 3] => [3, 2, 0, 1]
const SHUFFLE: u8 = 0b01_00_10_11;
let x: core::arch::x86_64::__m128 = unsafe { core::mem::transmute([0u32, 1u32, 2u32, 3u32]) };
let y: core::arch::x86_64::__m128;
// `pshufd` 와 같이 즉시값(immediate)을 기대하는 명령어에 상수 값을 전달합니다.
unsafe {
core::arch::asm!("pshufd {xmm}, {xmm}, {shuffle}",
xmm = inlateout(xmm_reg) x=>y,
shuffle = const SHUFFLE
);
}
let y: [u32; 4] = unsafe { core::mem::transmute(y) };
assert_eq!(y, [3, 2, 0, 1]);
}
}
label <block>- The address of the block is substituted into the asm template string. The assembly code may jump to the substituted address.
- For targets that distinguish between direct jumps and indirect jumps (e.g. x86-64 with
cf-protectionenabled), the assembly code must not jump to the substituted address indirectly. - After execution of the block, the
asm!expression returns. - The type of the block must be unit or
!(never). - The block starts a new safety context; unsafe operations within the
labelblock must be wrapped in an innerunsafeblock, even though the entireasm!expression is already wrapped inunsafe.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")]
unsafe {
core::arch::asm!("jmp {}", label {
println!("Hello from inline assembly label");
});
}
}
피연산자 표현식은 함수 호출 인자와 마찬가지로 왼쪽에서 오른쪽으로 평가됩니다. asm! 이 실행된 후, 출력값들은 왼쪽에서 오른쪽 순서로 기록됩니다. 이는 두 개의 출력이 동일한 장소를 가리킬 때 중요합니다. 해당 장소는 가장 오른쪽에 있는 출력값을 갖게 됩니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut y: i64;
// y는 첫 번째 출력이 아니라 두 번째 출력에서 값을 가져옵니다.
unsafe { core::arch::asm!("mov {}, 0", "mov {}, 1", out(reg) y, out(reg) y); }
assert_eq!(y, 1);
}
}
Because naked_asm! defines a whole function body and the compiler cannot emit any additional code to handle operands, it can only use sym and const operands.
Because global_asm! exists outside a function, it can only use sym and const operands.
fn main() {}
// 함수 내부에 있지 않으므로 레지스터 피연산자는 허용되지 않습니다.
#[cfg(target_arch = "x86_64")]
core::arch::global_asm!("", in(reg) 5);
// 오류: `in` 피연산자는 `global_asm!` 과 함께 사용될 수 없습니다.
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
fn main() {}
fn foo() {}
#[cfg(target_arch = "x86_64")]
// 하지만 `const` 와 `sym` 은 모두 허용됩니다.
core::arch::global_asm!("/* {} {} */", const 0, sym foo);
레지스터 피연산자
입력 및 출력 피연산자는 명시적 레지스터로 지정하거나, 레지스터 할당기가 레지스터를 선택할 수 있는 레지스터 클래스로 지정할 수 있습니다. 명시적 레지스터는 문자열 리터럴(예: "eax")로 지정되며, 레지스터 클래스는 식별자(예: reg)로 지정됩니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut y: i64;
// `reg` 또는 `eax` 와 같은 명시적 레지스터를 지정하여 정수 레지스터를 얻을 수 있습니다.
unsafe { core::arch::asm!("mov eax, {:e}", in(reg) 5, lateout("eax") y); }
assert_eq!(y, 5);
}
}
명시적 레지스터는 레지스터 별칭(예: ARM의 r14 대 lr) 및 레지스터의 더 작은 뷰(예: eax 대 rax)를 베이스 레지스터와 동일하게 취급함에 유의하십시오.
두 개의 입력 피연산자 또는 두 개의 출력 피연산자에 동일한 명시적 레지스터를 사용하는 것은 컴파일 타임 오류입니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// eax를 두 번 지정할 수 없습니다.
unsafe { core::arch::asm!("", in("eax") 5, in("eax") 4); }
// 오류: `eax` 레지스터가 `eax` 레지스터와 충돌합니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// ... 서로 다른 별칭을 사용하더라도 마찬가지입니다.
unsafe { core::arch::asm!("", in("ax") 5, in("rax") 4); }
// 오류: `rax` 레지스터가 `ax` 레지스터와 충돌합니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
또한, 입력 피연산자 또는 출력 피연산자에서 겹치는 레지스터(예: ARM VFP)를 사용하는 것도 컴파일 타임 오류입니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// al은 ax와 겹치므로 둘 다 지정할 수 없습니다.
unsafe { core::arch::asm!("", in("ax") 5, in("al") 4i8); }
// 오류: `al` 레지스터가 `ax` 레지스터와 충돌합니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
인라인 어셈블리의 피연산자로는 다음 타입들만 허용됩니다:
- 정수 (부호 있는 것과 부호 없는 것)
- 부동 소수점 숫자
- 포인터 (thin 포인터만)
- 함수 포인터
- SIMD 벡터 (
#[repr(simd)]로 정의되고Copy를 구현하는 구조체). 이는std::arch에 정의된 아키텍처별 벡터 타입(예: x86의__m128또는 ARM의int8x16_t)을 포함합니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
extern "C" fn foo() {}
// 정수는 허용됩니다...
let y: i64 = 5;
unsafe { core::arch::asm!("/* {} */", in(reg) y); }
// 포인터도 허용되며...
let py = &raw const y;
unsafe { core::arch::asm!("/* {} */", in(reg) py); }
// 부동 소수점 역시 허용됩니다...
let f = 1.0f32;
unsafe { core::arch::asm!("/* {} */", in(xmm_reg) f); }
// 함수 포인터와 simd 벡터까지도 허용됩니다.
let func: extern "C" fn() = foo;
unsafe { core::arch::asm!("/* {} */", in(reg) func); }
let z = unsafe { core::arch::x86_64::_mm_set_epi64x(1, 0) };
unsafe { core::arch::asm!("/* {} */", in(xmm_reg) z); }
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
struct Foo;
let x: Foo = Foo;
// 구조체와 같은 복합 타입은 허용되지 않습니다.
unsafe { core::arch::asm!("/* {} */", in(reg) x); }
// 오류: 인라인 어셈블리에 `Foo` 타입의 값을 사용할 수 없습니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
현재 지원되는 레지스터 클래스 목록입니다:
| 아키텍처 | 레지스터 클래스 | 레지스터 | LLVM 제약 코드 |
|---|---|---|---|
| x86 | reg | ax, bx, cx, dx, si, di, bp, r[8-15] (x86-64 전용) | r |
| x86 | reg_abcd | ax, bx, cx, dx | Q |
| x86-32 | reg_byte | al, bl, cl, dl, ah, bh, ch, dh | q |
| x86-64 | reg_byte* | al, bl, cl, dl, sil, dil, bpl, r[8-15]b | q |
| x86 | xmm_reg | xmm[0-7] (x86) xmm[0-15] (x86-64) | x |
| x86 | ymm_reg | ymm[0-7] (x86) ymm[0-15] (x86-64) | x |
| x86 | zmm_reg | zmm[0-7] (x86) zmm[0-31] (x86-64) | v |
| x86 | kreg | k[1-7] | Yk |
| x86 | kreg0 | k0 | 클로버(clobbers) 전용 |
| x86 | x87_reg | st([0-7]) | 클로버(clobbers) 전용 |
| x86 | mmx_reg | mm[0-7] | 클로버(clobbers) 전용 |
| x86-64 | tmm_reg | tmm[0-7] | 클로버(clobbers) 전용 |
| AArch64 | reg | x[0-30] | r |
| AArch64 | vreg | v[0-31] | w |
| AArch64 | vreg_low16 | v[0-15] | x |
| AArch64 | preg | p[0-15], ffr | 클로버(clobbers) 전용 |
| Arm64EC | reg | x[0-12], x[15-22], x[25-27], x30 | r |
| Arm64EC | vreg | v[0-15] | w |
| Arm64EC | vreg_low16 | v[0-15] | x |
| ARM (ARM/Thumb2) | reg | r[0-12], r14 | r |
| ARM (Thumb1) | reg | r[0-7] | r |
| ARM | sreg | s[0-31] | t |
| ARM | sreg_low16 | s[0-15] | x |
| ARM | dreg | d[0-31] | w |
| ARM | dreg_low16 | d[0-15] | t |
| ARM | dreg_low8 | d[0-8] | x |
| ARM | qreg | q[0-15] | w |
| ARM | qreg_low8 | q[0-7] | t |
| ARM | qreg_low4 | q[0-3] | x |
| RISC-V | reg | x1, x[5-7], x[9-15], x[16-31] (non-RV32E) | r |
| RISC-V | freg | f[0-31] | f |
| RISC-V | vreg | v[0-31] | 클로버(clobbers) 전용 |
| LoongArch | reg | $r1, $r[4-20], $r[23,30] | r |
| LoongArch | freg | $f[0-31] | f |
| s390x | reg | r[0-10], r[12-14] | r |
| s390x | reg_addr | r[1-10], r[12-14] | a |
| s390x | freg | f[0-15] | f |
| s390x | vreg | v[0-31] | 클로버(clobbers) 전용 |
| s390x | areg | a[2-15] | 클로버(clobbers) 전용 |
| PowerPC | reg | r0, r[3-12], r[14-28] | r |
| PowerPC | reg_nonzero | r[3-12], r[14-28] | b |
| PowerPC | spe_acc | spe_acc | 클로버(clobbers) 전용 |
| PowerPC64 | reg | r0, r[3-12], r[14-29] | r |
| PowerPC64 | reg_nonzero | r[3-12], r[14-29] | b |
| PowerPC/PowerPC64 | freg | f[0-31] | f |
| PowerPC/PowerPC64 | vreg | v[0-31] | v |
| PowerPC/PowerPC64 | vsreg | vs[0-63] | wa |
| PowerPC/PowerPC64 | cr | cr[0-7], cr | 클로버(clobbers) 전용 |
| PowerPC/PowerPC64 | ctr | ctr | 클로버(clobbers) 전용 |
| PowerPC/PowerPC64 | lr | lr | 클로버(clobbers) 전용 |
| PowerPC/PowerPC64 | xer | xer | 클로버(clobbers) 전용 |
Note
- x86에서
reg_byte는reg와 다르게 취급됩니다. 그 이유는 컴파일러가al과ah를 별도로 할당할 수 있는 반면,reg는 레지스터 전체를 예약하기 때문입니다.- x86-64에서 고위 바이트 레지스터(예:
ah)는reg_byte레지스터 클래스에서 사용할 수 없습니다.- 일부 레지스터 클래스는 “클로버 전용(Only clobbers)“으로 표시되어 있습니다. 이는 해당 클래스의 레지스터를 입력이나 출력에 사용할 수 없으며, 오직
out(<explicit register>) _또는lateout(<explicit register>) _형식의 클로버로만 사용할 수 있음을 의미합니다.- The
spe_accregister is only available on PowerPC SPE targets.
각 레지스터 클래스는 사용할 수 있는 값의 타입에 대한 제약 조건을 가집니다. 이는 값이 레지스터에 로드되는 방식이 타입에 따라 다르기 때문에 필요합니다. 예를 들어, 빅 엔디안(big-endian) 시스템에서 i32x4 와 i8x16 을 SIMD 레지스터에 로드하면, 두 값의 바이트 단위 메모리 표현이 동일하더라도 레지스터의 내용은 서로 다를 수 있습니다. 특정 레지스터 클래스에서 지원되는 타입의 가용 여부는 현재 활성화된 타겟 기능(target features)에 따라 달라질 수 있습니다.
| 아키텍처 | 레지스터 클래스 | 타겟 기능 | 허용되는 타입 |
|---|---|---|---|
| x86-32 | reg | 없음 | i16, i32, f32 |
| x86-64 | reg | 없음 | i16, i32, f32, i64, f64 |
| x86 | reg_byte | 없음 | i8 |
| x86 | xmm_reg | sse | i32, f32, i64, f64, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 |
| x86 | ymm_reg | avx | i32, f32, i64, f64, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 i8x32, i16x16, i32x8, i64x4, f32x8, f64x4 |
| x86 | zmm_reg | avx512f | i32, f32, i64, f64, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 i8x32, i16x16, i32x8, i64x4, f32x8, f64x4 i8x64, i16x32, i32x16, i64x8, f32x16, f64x8 |
| x86 | kreg | avx512f | i8, i16 |
| x86 | kreg | avx512bw | i32, i64 |
| x86 | mmx_reg | 해당 없음 (N/A) | 클로버(clobbers) 전용 |
| x86 | x87_reg | 해당 없음 (N/A) | 클로버(clobbers) 전용 |
| x86 | tmm_reg | 해당 없음 (N/A) | 클로버(clobbers) 전용 |
| AArch64 | reg | 없음 | i8, i16, i32, f32, i64, f64 |
| AArch64 | vreg | neon | i8, i16, i32, f32, i64, f64, i8x8, i16x4, i32x2, i64x1, f32x2, f64x1, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 |
| AArch64 | preg | 해당 없음 (N/A) | 클로버(clobbers) 전용 |
| Arm64EC | reg | 없음 | i8, i16, i32, f32, i64, f64 |
| Arm64EC | vreg | neon | i8, i16, i32, f32, i64, f64, i8x8, i16x4, i32x2, i64x1, f32x2, f64x1, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 |
| ARM | reg | 없음 | i8, i16, i32, f32 |
| ARM | sreg | vfp2 | i32, f32 |
| ARM | dreg | vfp2 | i64, f64, i8x8, i16x4, i32x2, i64x1, f32x2 |
| ARM | qreg | neon | i8x16, i16x8, i32x4, i64x2, f32x4 |
| RISC-V32 | reg | 없음 | i8, i16, i32, f32 |
| RISC-V64 | reg | 없음 | i8, i16, i32, f32, i64, f64 |
| RISC-V | freg | f | f32 |
| RISC-V | freg | d | f64 |
| RISC-V | vreg | 해당 없음 (N/A) | 클로버(clobbers) 전용 |
| LoongArch32 | reg | 없음 | i8, i16, i32, f32 |
| LoongArch64 | reg | 없음 | i8, i16, i32, i64, f32, f64 |
| LoongArch | freg | f | f32 |
| LoongArch | freg | d | f64 |
| s390x | reg, reg_addr | 없음 | i8, i16, i32, i64 |
| s390x | freg | 없음 | f32, f64 |
| s390x | vreg | 해당 없음 (N/A) | 클로버(clobbers) 전용 |
| s390x | areg | 해당 없음 (N/A) | 클로버(clobbers) 전용 |
| PowerPC | spe_acc | 없음 | 클로버(clobbers) 전용 |
| PowerPC/PowerPC64 | reg | 없음 | i8, i16, i32, i64 (PowerPC64 only) |
| PowerPC/PowerPC64 | reg_nonzero | 없음 | i8, i16, i32, i64 (PowerPC64 only) |
| PowerPC/PowerPC64 | freg | 없음 | f32, f64 |
| PowerPC/PowerPC64 | vreg | altivec | i8x16, i16x8, i32x4, f32x4 |
| PowerPC/PowerPC64 | vreg | vsx | f32, f64, i64x2, f64x2 |
| PowerPC/PowerPC64 | vsreg | vsx | The union of vsx and altivec vreg types |
| PowerPC/PowerPC64 | cr | 없음 | 클로버(clobbers) 전용 |
| PowerPC/PowerPC64 | ctr | 없음 | 클로버(clobbers) 전용 |
| PowerPC/PowerPC64 | lr | 없음 | 클로버(clobbers) 전용 |
| PowerPC/PowerPC64 | xer | 없음 | 클로버(clobbers) 전용 |
Note
For the purposes of the above table pointers, function pointers and
isize/usizeare treated as the equivalent integer type (i16/i32/i64depending on the target).
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x = 5i32;
let y = -1i8;
let z = unsafe { core::arch::x86_64::_mm_set_epi64x(1, 0) };
// `reg` 는 `i32` 에 유효하고, `reg_byte` 는 `i8` 에 유효하며, `xmm_reg` 는 `__m128i` 에 유효합니다.
// `tmm0` 는 입력이나 출력으로 사용할 수 없지만, 클로버(clobber)할 수는 있습니다.
unsafe { core::arch::asm!("/* {} {} {} */", in(reg) x, in(reg_byte) y, in(xmm_reg) z, out("tmm0") _); }
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let z = unsafe { core::arch::x86_64::_mm_set_epi64x(1, 0) };
// `__m128i` 를 `reg` 입력으로 전달할 수 없습니다.
unsafe { core::arch::asm!("/* {} */", in(reg) z); }
// 오류: `__m128i` 타입은 이 레지스터 클래스에 사용할 수 없습니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
만약 할당된 레지스터보다 값의 크기가 작다면, 해당 레지스터의 상위 비트들은 입력 시 정의되지 않은(undefined) 값을 갖게 되며 출력 시에는 무시됩니다. 유일한 예외는 RISC-V의 freg 레지스터 클래스로, RISC-V 아키텍처의 요구 사항에 따라 f32 값은 f64 내에 NaN-boxed 됩니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x: i64;
// 32비트 값을 64비트 값으로 옮기기, 이런.
#[allow(asm_sub_register)] // rustc는 이 동작에 대해 경고합니다.
unsafe { core::arch::asm!("mov {}, {}", lateout(reg) x, in(reg) 4i32); }
// 상위 32비트는 불확정적입니다.
assert_eq!(x, 4); // 이 단언(assertion)은 성공이 보장되지 않습니다.
assert_eq!(x & 0xFFFFFFFF, 4); // 하지만 이것은 성공할 것입니다.
}
}
inout 피연산자에 대해 별도의 입력 및 출력 표현식이 지정된 경우, 두 표현식은 반드시 동일한 타입을 가져야 합니다. 유일한 예외는 두 피연산자가 모두 포인터이거나 정수인 경우로, 이 경우에는 크기만 같으면 됩니다. 이 제한은 LLVM 및 GCC의 레지스터 할당기가 서로 다른 타입을 가진 묶인(tied) 피연산자를 처리하지 못하는 경우가 있기 때문에 존재합니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// 포인터와 정수는 (크기가 같다면) 혼용될 수 있습니다.
let x: isize = 0;
let y: *mut ();
// 인라인 어셈블리의 마법을 사용하여 `isize` 를 `*mut ()` 로 변환(transmute)합니다.
unsafe { core::arch::asm!("/*{}*/", inout(reg) x=>y); }
assert!(y.is_null()); // 널 포인터를 만드는 지극히 우회적인 방법
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i32 = 0;
let y: f32;
// 하지만 이런 식으로 `i32` 를 `f32` 로 재해석할 수는 없습니다.
unsafe { core::arch::asm!("/* {} */", inout(reg) x=>y); }
// 오류: asm inout 인자의 타입이 호환되지 않습니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
레지스터 이름
일부 레지스터는 여러 개의 이름을 가집니다. 이들은 모두 컴파일러에 의해 기본 레지스터 이름과 동일하게 취급됩니다. 다음은 지원되는 모든 레지스터 별칭 목록입니다.
| 아키텍처 | 기본 레지스터 | 별칭 |
|---|---|---|
| x86 | ax | eax, rax |
| x86 | bx | ebx, rbx |
| x86 | cx | ecx, rcx |
| x86 | dx | edx, rdx |
| x86 | si | esi, rsi |
| x86 | di | edi, rdi |
| x86 | bp | bpl, ebp, rbp |
| x86 | sp | spl, esp, rsp |
| x86 | ip | eip, rip |
| x86 | st(0) | st |
| x86 | r[8-15] | r[8-15]b, r[8-15]w, r[8-15]d |
| x86 | xmm[0-31] | ymm[0-31], zmm[0-31] |
| AArch64 | x[0-30] | w[0-30] |
| AArch64 | x29 | fp |
| AArch64 | x30 | lr |
| AArch64 | sp | wsp |
| AArch64 | xzr | wzr |
| AArch64 | v[0-31] | b[0-31], h[0-31], s[0-31], d[0-31], q[0-31] |
| Arm64EC | x[0-30] | w[0-30] |
| Arm64EC | x29 | fp |
| Arm64EC | x30 | lr |
| Arm64EC | sp | wsp |
| Arm64EC | xzr | wzr |
| Arm64EC | v[0-15] | b[0-15], h[0-15], s[0-15], d[0-15], q[0-15] |
| ARM | r[0-3] | a[1-4] |
| ARM | r[4-9] | v[1-6] |
| ARM | r9 | rfp |
| ARM | r10 | sl |
| ARM | r11 | fp |
| ARM | r12 | ip |
| ARM | r13 | sp |
| ARM | r14 | lr |
| ARM | r15 | pc |
| RISC-V | x0 | zero |
| RISC-V | x1 | ra |
| RISC-V | x2 | sp |
| RISC-V | x3 | gp |
| RISC-V | x4 | tp |
| RISC-V | x[5-7] | t[0-2] |
| RISC-V | x8 | fp, s0 |
| RISC-V | x9 | s1 |
| RISC-V | x[10-17] | a[0-7] |
| RISC-V | x[18-27] | s[2-11] |
| RISC-V | x[28-31] | t[3-6] |
| RISC-V | f[0-7] | ft[0-7] |
| RISC-V | f[8-9] | fs[0-1] |
| RISC-V | f[10-17] | fa[0-7] |
| RISC-V | f[18-27] | fs[2-11] |
| RISC-V | f[28-31] | ft[8-11] |
| LoongArch | $r0 | $zero |
| LoongArch | $r1 | $ra |
| LoongArch | $r2 | $tp |
| LoongArch | $r3 | $sp |
| LoongArch | $r[4-11] | $a[0-7] |
| LoongArch | $r[12-20] | $t[0-8] |
| LoongArch | $r21 | |
| LoongArch | $r22 | $fp, $s9 |
| LoongArch | $r[23-31] | $s[0-8] |
| LoongArch | $f[0-7] | $fa[0-7] |
| LoongArch | $f[8-23] | $ft[0-15] |
| LoongArch | $f[24-31] | $fs[0-7] |
| PowerPC/PowerPC64 | r1 | sp |
| PowerPC/PowerPC64 | r31 | fp |
| PowerPC/PowerPC64 | r[0-31] | [0-31] |
| PowerPC/PowerPC64 | f[0-31] | fr[0-31] |
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let z = 0i64;
// rax는 eax 및 ax의 별칭입니다.
unsafe { core::arch::asm!("", in("rax") z); }
}
}
일부 레지스터는 입력 또는 출력 피연산자로 사용할 수 없습니다.
| 아키텍처 | 지원되지 않는 레지스터 | 이유 |
|---|---|---|
| 모두 | sp, r15 (s390x), r1 (PowerPC and PowerPC64) | The stack pointer must be restored to its original value at the end of the assembly code or before jumping to a label block. |
| 모두 | bp (x86), x29 (AArch64 and Arm64EC), x8 (RISC-V), $fp (LoongArch), r11 (s390x), fp (PowerPC and PowerPC64) | 프레임 포인터(fp)는 입력 또는 출력으로 사용할 수 없습니다. |
| ARM | r7 또는 r11 | ARM에서 프레임 포인터는 타겟에 따라 r7 또는 r11 이 될 수 있습니다. 프레임 포인터는 입력 또는 출력으로 사용할 수 없습니다. |
| 모두 | si (x86-32), bx (x86-64), r6 (ARM), x19 (AArch64 and Arm64EC), x9 (RISC-V), $s8 (LoongArch), r29 and r30 (PowerPC), r30 (PowerPC64) | 이는 복잡한 스택 프레임을 가진 함수를 위해 LLVM에서 내부적으로 “베이스 포인터“로 사용됩니다. |
| x86 | ip | 이것은 프로그램 카운터(pc)이며, 실제 레지스터가 아닙니다. |
| AArch64 | xzr | 이것은 수정할 수 없는 상수 제로 레지스터입니다. |
| AArch64 | x18 | 이는 일부 AArch64 타겟에서 OS가 예약한 레지스터입니다. |
| Arm64EC | xzr | 이것은 수정할 수 없는 상수 제로 레지스터입니다. |
| Arm64EC | x18 | 이것은 OS가 예약한 레지스터입니다. |
| Arm64EC | x13, x14, x23, x24, x28, v[16-31], p[0-15], ffr | 이들은 Arm64EC에서 지원되지 않는 AArch64 레지스터들입니다. |
| ARM | pc | 이것은 프로그램 카운터(pc)이며, 실제 레지스터가 아닙니다. |
| ARM | r9 | 이는 일부 ARM 타겟에서 OS가 예약한 레지스터입니다. |
| RISC-V | x0 | 이것은 수정할 수 없는 상수 제로 레지스터입니다. |
| RISC-V | gp, tp | 이 레지스터들은 예약되어 있으며 입력이나 출력으로 사용할 수 없습니다. |
| LoongArch | $r0 또는 $zero | 이것은 수정할 수 없는 상수 제로 레지스터입니다. |
| LoongArch | $r2 또는 $tp | 이는 TLS를 위해 예약되어 있습니다. |
| LoongArch | $r21 | 이는 ABI에 의해 예약되어 있습니다. |
| s390x | c[0-15] | 커널에 의해 예약되어 있습니다. |
| s390x | a[0-1] | 시스템 사용을 위해 예약되어 있습니다. |
| PowerPC/PowerPC64 | r2, r13 | These are system reserved registers. |
| PowerPC/PowerPC64 | vrsave | The vrsave register cannot be used as an input or output. |
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// bp는 예약되어 있습니다.
unsafe { core::arch::asm!("", in("bp") 5i32); }
// 오류: 잘못된 레지스터 `bp`: 프레임 포인터는 인라인 asm의 피연산자로 사용할 수 없습니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
프레임 포인터와 베이스 포인터 레지스터는 LLVM 내부 사용을 위해 예약되어 있습니다. asm! 구문에서 예약된 레지스터 사용을 명시적으로 지정할 수는 없지만, 일부 경우 LLVM이 reg 피연산자를 위해 이러한 예약된 레지스터 중 하나를 할당할 수 있습니다. 예약된 레지스터를 사용하는 어셈블리 코드는 reg 피연산자가 동일한 레지스터를 사용할 수 있으므로 주의해야 합니다.
템플릿 수식어
플레이스홀더는 중괄호 안의 : 뒤에 수식어를 지정하여 기능을 확장할 수 있습니다. 이러한 수식어는 레지스터 할당에는 영향을 주지 않지만, 피연산자가 템플릿 문자열에 삽입될 때 포맷팅되는 방식을 변경합니다.
템플릿 플레이스홀더당 하나의 수식어만 허용됩니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// `r` 과 `e` 를 동시에 지정할 수는 없습니다.
unsafe { core::arch::asm!("/* {:er}", in(reg) 5i32); }
// 오류: asm 템플릿 수식어는 단일 문자여야 합니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
지원되는 수식어는 LLVM(및 GCC)의 asm 템플릿 인자 수식어 의 하위 집합이지만, 동일한 문자 코드를 사용하지는 않습니다.
| 아키텍처 | 레지스터 클래스 | 수식어 | 출력 예시 | LLVM 수식어 |
|---|---|---|---|---|
| x86-32 | reg | 없음 | eax | k |
| x86-64 | reg | 없음 | rax | q |
| x86-32 | reg_abcd | l | al | b |
| x86-64 | reg | l | al | b |
| x86 | reg_abcd | h | ah | h |
| x86 | reg | x | ax | w |
| x86 | reg | e | eax | k |
| x86-64 | reg | r | rax | q |
| x86 | reg_byte | 없음 | al / ah | 없음 |
| x86 | xmm_reg | 없음 | xmm0 | x |
| x86 | ymm_reg | 없음 | ymm0 | t |
| x86 | zmm_reg | 없음 | zmm0 | g |
| x86 | *mm_reg | x | xmm0 | x |
| x86 | *mm_reg | y | ymm0 | t |
| x86 | *mm_reg | z | zmm0 | g |
| x86 | kreg | 없음 | k1 | 없음 |
| AArch64/Arm64EC | reg | 없음 | x0 | x |
| AArch64/Arm64EC | reg | w | w0 | w |
| AArch64/Arm64EC | reg | x | x0 | x |
| AArch64/Arm64EC | vreg | 없음 | v0 | 없음 |
| AArch64/Arm64EC | vreg | v | v0 | 없음 |
| AArch64/Arm64EC | vreg | b | b0 | b |
| AArch64/Arm64EC | vreg | h | h0 | h |
| AArch64/Arm64EC | vreg | s | s0 | s |
| AArch64/Arm64EC | vreg | d | d0 | d |
| AArch64/Arm64EC | vreg | q | q0 | q |
| ARM | reg | 없음 | r0 | 없음 |
| ARM | sreg | 없음 | s0 | 없음 |
| ARM | dreg | 없음 | d0 | P |
| ARM | qreg | 없음 | q0 | q |
| ARM | qreg | e / f | d0 / d1 | e / f |
| RISC-V | reg | 없음 | x1 | 없음 |
| RISC-V | freg | 없음 | f0 | 없음 |
| LoongArch | reg | 없음 | $r1 | 없음 |
| LoongArch | freg | 없음 | $f0 | 없음 |
| s390x | reg | 없음 | %r0 | 없음 |
| s390x | reg_addr | 없음 | %r1 | 없음 |
| s390x | freg | 없음 | %f0 | 없음 |
| PowerPC/PowerPC64 | reg | 없음 | 0 | 없음 |
| PowerPC/PowerPC64 | reg_nonzero | 없음 | 3 | 없음 |
| PowerPC/PowerPC64 | freg | 없음 | 0 | 없음 |
| PowerPC/PowerPC64 | vreg | 없음 | 0 | 없음 |
| PowerPC/PowerPC64 | vsreg | 없음 | 0 | 없음 |
Note
- ARM의
e/f: 이는 NEON 쿼드(128비트) 레지스터의 하위 또는 상위 더블워드(doubleword) 레지스터 이름을 출력합니다.- x86: 수식어가 없는
reg에 대한 우리의 동작은 GCC와 다릅니다. GCC는 피연산자 값 타입을 기반으로 수식어를 추론하지만, 우리는 기본적으로 전체 레지스터 크기를 사용합니다.- x86
xmm_reg:x,t및gLLVM 수식어는 아직 LLVM에 구현되지 않았지만(이들은 GCC에서만 지원됨), 이는 간단한 변경사항일 것입니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x = 0x10u16;
// `xchg` 를 사용한 u16::swap_bytes
// `{x}` 의 하위 절반은 `{x:l}` 로, 상위 절반은 `{x:h}` 로 참조됩니다.
unsafe { core::arch::asm!("xchg {x:l}, {x:h}", x = inout(reg_abcd) x); }
assert_eq!(x, 0x1000u16);
}
}
As stated in the previous section, passing an input value smaller than the register width will result in the upper bits of the register containing undefined values. This is not a problem if the inline asm only accesses the lower bits of the register, which can be done by using a template modifier to use a subregister name in the assembly code (e.g. ax instead of rax). Since this an easy pitfall, the compiler will suggest a template modifier to use where appropriate given the input type. If all references to an operand already have modifiers then the warning is suppressed for that operand.
ABI 클로버(Clobbers)
The clobber_abi keyword can be used to apply a default set of clobbers to the assembly code. This will automatically insert the necessary clobber constraints as needed for calling a function with a particular calling convention: if the calling convention does not fully preserve the value of a register across a call then lateout("...") _ is implicitly added to the operands list (where the ... is replaced by the register’s name).
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
extern "C" fn foo() -> i32 { 0 }
let z: i32;
// 함수를 호출하려면, 피호출자 저장(callee saved) 레지스터를 클로버하고 있음을 컴파일러에 알려야 합니다.
unsafe { core::arch::asm!("call {}", sym foo, out("rax") z, clobber_abi("C")); }
assert_eq!(z, 0);
}
}
clobber_abi 는 횟수 제한 없이 지정될 수 있습니다. 지정된 모든 호출 규약의 합집합에 속하는 모든 고유 레지스터에 대해 클로버를 삽입합니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
extern "sysv64" fn foo() -> i32 { 0 }
extern "win64" fn bar(x: i32) -> i32 { x + 1 }
let z: i32;
// 심지어 서로 다른 규약과 서로 다른 저장된 레지스터를 가진 여러 함수를 호출할 수도 있습니다.
unsafe {
core::arch::asm!(
"call {}",
"mov ecx, eax",
"call {}",
sym foo,
sym bar,
out("rax") z,
clobber_abi("sysv64"),
clobber_abi("win64"),
);
}
assert_eq!(z, 1);
}
}
clobber_abi 가 사용될 때 컴파일러는 제네릭 레지스터 클래스 출력을 허용하지 않습니다. 모든 출력은 명시적 레지스터를 지정해야 합니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
extern "C" fn foo(x: i32) -> i32 { 0 }
let z: i32;
// 실수로 겹치지 않도록 명시적 레지스터를 사용해야 합니다.
unsafe {
core::arch::asm!(
"mov eax, {:e}",
"call {}",
out(reg) z,
sym foo,
clobber_abi("C")
);
// 오류: `clobber_abi` 를 사용하는 asm은 출력에 대해 명시적 레지스터를 지정해야 합니다.
}
assert_eq!(z, 0);
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
명시적 레지스터 출력은 clobber_abi 에 의해 삽입된 암시적 클로버보다 우선순위를 갖습니다. 레지스터가 출력으로 사용되지 않는 경우에만 해당 레지스터에 대한 클로버가 삽입됩니다.
다음 ABI들은 clobber_abi 와 함께 사용될 수 있습니다:
| 아키텍처 | ABI 이름 | 클로버된 레지스터 |
|---|---|---|
| x86-32 | "C", "system", "efiapi", "cdecl", "stdcall", "fastcall" | ax, cx, dx, xmm[0-7], mm[0-7], k[0-7], st([0-7]) |
| x86-64 | "C", "system" (윈도우 전용), "efiapi", "win64" | ax, cx, dx, r[8-11], xmm[0-31], mm[0-7], k[0-7], st([0-7]), tmm[0-7] |
| x86-64 | "C", "system" (윈도우 제외), "sysv64" | ax, cx, dx, si, di, r[8-11], xmm[0-31], mm[0-7], k[0-7], st([0-7]), tmm[0-7] |
| AArch64 | "C", "system", "efiapi" | x[0-17], x18*, x30, v[0-31], p[0-15], ffr |
| Arm64EC | "C", "system" | x[0-12], x[15-17], x30, v[0-15] |
| ARM | "C", "system", "efiapi", "aapcs" | r[0-3], r12, r14, s[0-15], d[0-7], d[16-31] |
| RISC-V | "C", "system", "efiapi" | x1, x[5-7], x[10-17], x[28-31], f[0-7], f[10-17], f[28-31], v[0-31] |
| LoongArch | "C", "system" | $r1, $r[4-20], $f[0-23] |
| s390x | "C", "system" | r[0-5], r14, f[0-7], v[0-31], a[2-15] |
Note
- AArch64에서
x18은 타겟에서 예약된 레지스터로 간주되지 않는 경우에만 클로버 목록에 포함됩니다.- RISC-V에서
x[16-17]및x[28-31]은 타겟에서 예약된 레지스터로 간주되지 않는 경우에만 클로버 목록에 포함됩니다.
각 ABI에 대한 클로버된 레지스터 목록은 아키텍처에 새로운 레지스터가 추가됨에 따라 rustc에서 업데이트됩니다. 이는 LLVM이 생성된 코드에서 이러한 새로운 레지스터를 사용하기 시작할 때 asm! 클로버가 계속해서 올바르게 작동하도록 보장합니다.
옵션
Flags are used to further influence the behavior of the inline assembly code. Currently the following options are defined:
pure: The assembly code has no side effects, must eventually return, and its outputs depend only on its direct inputs (i.e. the values themselves, not what they point to) or values read from memory (unless thenomemoptions is also set). This allows the compiler to execute the assembly code fewer times than specified in the program (e.g. by hoisting it out of a loop) or even eliminate it entirely if the outputs are not used. Thepureoption must be combined with either thenomemorreadonlyoptions, otherwise a compile-time error is emitted.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i32 = 0;
let z: i32;
// `pure` 는 어셈블리에 부작용이 없다고 가정하여 최적화하는 데 사용될 수 있습니다.
unsafe { core::arch::asm!("inc {}", inout(reg) x => z, options(pure, nomem)); }
assert_eq!(z, 1);
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i32 = 0;
let z: i32;
// 메모리 읽기 허용 여부를 나타내기 위해 nomem 또는 readonly 중 하나를 만족해야 합니다.
unsafe { core::arch::asm!("inc {}", inout(reg) x => z, options(pure)); }
// 오류: `pure` 옵션은 `nomem` 또는 `readonly` 중 하나와 결합되어야 합니다.
assert_eq!(z, 0);
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
nomem: The assembly code does not read from or write to any memory accessible outside of the assembly code. This allows the compiler to cache the values of modified global variables in registers across execution of the assembly code since it knows that they are not read from or written to by it. The compiler also assumes that the assembly code does not perform any kind of synchronization with other threads, e.g. via fences.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x = 0i32;
let z: i32;
// Accessing outside memory from assembly when `nomem` is
// specified is disallowed
unsafe {
core::arch::asm!("mov {val:e}, dword ptr [{ptr}]",
ptr = in(reg) &mut x,
val = lateout(reg) z,
options(nomem)
)
}
// Writing to outside memory from assembly when `nomem` is
// specified is also undefined behaviour
unsafe {
core::arch::asm!("mov dword ptr [{ptr}], {val:e}",
ptr = in(reg) &mut x,
val = in(reg) z,
options(nomem)
)
}
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i32 = 0;
let z: i32;
// 하지만 `push` 등을 통해 우리만의 메모리를 할당한다면
// 여전히 그것을 사용할 수 있습니다.
unsafe {
core::arch::asm!("push {x}", "add qword ptr [rsp], 1", "pop {x}",
x = inout(reg) x => z,
options(nomem)
);
}
assert_eq!(z, 1);
}
}
readonly: The assembly code does not write to any memory accessible outside of the assembly code. This allows the compiler to cache the values of unmodified global variables in registers across execution of the assembly code since it knows that they are not written to by it. The compiler also assumes that this assembly code does not perform any kind of synchronization with other threads, e.g. via fences.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let mut x = 0;
// We cannot modify outside memory when `readonly` is specified
unsafe {
core::arch::asm!("mov dword ptr[{}], 1", in(reg) &mut x, options(readonly))
}
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64 = 0;
let z: i64;
// 하지만 여전히 읽을 수는 있습니다.
unsafe {
core::arch::asm!("mov {x}, qword ptr [{x}]",
x = inout(reg) &x => z,
options(readonly)
);
}
assert_eq!(z, 0);
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i64 = 0;
let z: i64;
// nomem과 동일한 예외가 적용됩니다.
unsafe {
core::arch::asm!("push {x}", "add qword ptr [rsp], 1", "pop {x}",
x = inout(reg) x => z,
options(readonly)
);
}
assert_eq!(z, 1);
}
}
preserves_flags: The assembly code does not modify the flags register (defined in the rules below). This allows the compiler to avoid recomputing the condition flags after execution of the assembly code.
noreturn: The assembly code does not fall through; behavior is undefined if it does. It may still jump tolabelblocks. If anylabelblocks return unit, theasm!block will return unit. Otherwise it will return!(never). As with a call to a function that does not return, local variables in scope are not dropped before execution of the assembly code.
fn main() -> ! {
#[cfg(target_arch = "x86_64")] {
// noreturn 블록 내부에서 실행을 트랩(trap)하기 위해 명령어를 사용할 수 있습니다.
unsafe { core::arch::asm!("ud2", options(noreturn)); }
}
#[cfg(not(target_arch = "x86_64"))] panic!("no return");
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// noreturn asm 블록의 끝을 지나치지 않도록 할 책임은 사용자에게 있습니다.
unsafe { core::arch::asm!("", options(noreturn)); }
}
}
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")]
let _: () = unsafe {
// You may still jump to a `label` block
core::arch::asm!("jmp {}", label {
println!();
}, options(noreturn));
};
}
nostack: The assembly code does not push data to the stack, or write to the stack red-zone (if supported by the target). If this option is not used then the stack pointer is guaranteed by the compiler at the start of the assembly code to be suitably aligned (according to the target ABI) for a function call.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// nostack과 함께 `push` 및 `pop` 을 사용하는 것은 정의되지 않은 동작(UB)입니다.
unsafe { core::arch::asm!("push rax", "pop rax", options(nostack)); }
}
}
att_syntax: 이 옵션은 x86에서만 유효하며, 어셈블러가 GNU 어셈블러의.att_syntax prefix모드를 사용하도록 합니다. 레지스터 피연산자는 앞에%가 붙은 상태로 치환됩니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let x: i32;
let y = 1i32;
// 여기서는 AT&T 구문을 사용해야 합니다. 피연산자 순서는 src, dest입니다.
unsafe {
core::arch::asm!("mov {y:e}, {x:e}",
x = lateout(reg) x,
y = in(reg) y,
options(att_syntax)
);
}
assert_eq!(x, y);
}
}
raw: 템플릿 문자열을 원시 어셈블리 문자열로 파싱하게 하며,{및}에 대한 특별한 처리를 하지 않습니다. 이는 주로include_str!을 사용하여 외부 파일에서 원시 어셈블리 코드를 포함할 때 유용합니다.
컴파일러는 옵션에 대해 몇 가지 추가 검사를 수행합니다.
nomem과readonly옵션은 상호 배타적입니다. 두 옵션을 모두 지정하는 것은 컴파일 타임 오류입니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// nomem은 readonly보다 엄격하게 강력하므로 함께 지정할 수 없습니다.
unsafe { core::arch::asm!("", options(nomem, readonly)); }
// 오류: `nomem` 과 `readonly` 옵션은 상호 배타적입니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
pure를 출력이 없거나 버려지는 출력(_)만 있는 asm 블록에 지정하는 것은 컴파일 타임 오류입니다.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
// pure 블록은 최소 하나 이상의 출력이 필요합니다.
unsafe { core::arch::asm!("", options(pure)); }
// 오류: `pure` 옵션을 사용하는 asm은 최소 하나 이상의 출력이 있어야 합니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
- It is a compile-time error to specify
noreturnon an asm block with outputs and without labels.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let z: i32;
// noreturn은 출력을 가질 수 없습니다.
unsafe { core::arch::asm!("mov {:e}, 1", out(reg) z, options(noreturn)); }
// 오류: `noreturn` 옵션에서는 asm 출력이 허용되지 않습니다.
}
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
}
- It is a compile-time error to have any
labelblocks in an asm block with outputs.
naked_asm! only supports the att_syntax and raw options. The remaining options are not meaningful because the inline assembly defines the whole function body.
global_asm! only supports the att_syntax and raw options. The remaining options are not meaningful for global-scope inline assembly.
fn main() {}
#[cfg(target_arch = "x86_64")]
// global_asm!에서 nomem은 무의미합니다.
core::arch::global_asm!("", options(nomem));
#[cfg(not(target_arch = "x86_64"))] core::compile_error!("이 아키텍처에서는 테스트가 지원되지 않습니다");
인라인 어셈블리 규칙
정의되지 않은 동작을 피하기 위해, 함수 스코프 인라인 어셈블리(asm!)를 사용할 때는 다음 규칙들을 따라야 합니다.
- Any registers not specified as inputs will contain an undefined value on entry to the assembly code.
- 인라인 어셈블리 문맥에서 “정의되지 않은 값“이란, 해당 레지스터가 아키텍처에서 허용하는 가능한 값들 중 하나를 (비결정적으로) 가질 수 있음을 의미합니다. 특히 이는 읽을 때마다 값이 달라질 수 있는 LLVM의
undef와는 다릅니다(어셈블리 코드에는 그러한 개념이 존재하지 않기 때문입니다).
- 인라인 어셈블리 문맥에서 “정의되지 않은 값“이란, 해당 레지스터가 아키텍처에서 허용하는 가능한 값들 중 하나를 (비결정적으로) 가질 수 있음을 의미합니다. 특히 이는 읽을 때마다 값이 달라질 수 있는 LLVM의
- Any registers not specified as outputs must have the same value upon exiting the assembly code as they had on entry, otherwise behavior is undefined.
- 이는 입력 또는 출력으로 지정될 수 있는 레지스터에만 적용됩니다. 다른 레지스터들은 타겟별 규칙을 따릅니다.
lateout이in과 동일한 레지스터에 할당될 수 있으며, 이 경우 이 규칙은 적용되지 않음에 유의하십시오. 하지만 이는 레지스터 할당 결과에 의존하므로 코드가 이에 의존해서는 안 됩니다.
- Behavior is undefined if execution unwinds out of the assembly code.
- 어셈블리 코드가 호출한 함수가 언와인드되는 경우에도 마찬가지로 적용됩니다.
- 어셈블리 코드가 읽고 쓸 수 있도록 허용된 메모리 위치의 집합은 FFI 함수에 허용된 것과 동일합니다.
- 만약
readonly옵션이 설정되어 있다면, 메모리 읽기만 허용됩니다. - 만약
nomem옵션이 설정되어 있다면, 메모리에 대한 어떠한 읽기나 쓰기도 허용되지 않습니다. - These rules do not apply to memory which is private to the assembly code, such as stack space allocated within it.
- 만약
- The compiler cannot assume that the instructions in the assembly code are the ones that will actually end up executed.
- This effectively means that the compiler must treat the assembly code as a black box and only take the interface specification into account, not the instructions themselves.
- 런타임 코드 패칭(patching)은 타겟별 메커니즘을 통해 허용됩니다.
- However there is no guarantee that each block of assembly code in the source directly corresponds to a single instance of instructions in the object file; the compiler is free to duplicate or deduplicate the assembly code in
asm!blocks.
- Unless the
nostackoption is set, assembly code is allowed to use stack space below the stack pointer.- On entry to the assembly code the stack pointer is guaranteed to be suitably aligned (according to the target ABI) for a function call.
- 스택 오버플로가 발생하지 않도록 할 책임은 사용자에게 있습니다(예: 가드 페이지에 도달하도록 스택 프로빙(stack probing) 사용).
- 타겟 ABI의 요구에 따라 스택 메모리를 할당할 때는 스택 포인터를 조정해야 합니다.
- The stack pointer must be restored to its original value before leaving the assembly code.
- Unless the
nostackoption is set, assembly code is allowed to modify the caller’s stack frame when the target ABI requires storing certain values in the caller’s frame (e.g., when saving thelron PowerPC64).
- If the
noreturnoption is set then behavior is undefined if execution falls through the end of the assembly code.
pure옵션이 설정된 경우,asm!이 직접적인 출력 이외의 부작용을 가지면 동작은 정의되지 않습니다. 또한 동일한 입력에 대해 두 번의asm!실행이 서로 다른 출력을 내는 경우에도 동작은 정의되지 않습니다.nomem옵션과 함께 사용될 때, “입력“은 오직asm!의 직접적인 입력만을 의미합니다.- When used with the
readonlyoption, “inputs” comprise the direct inputs of the assembly code and any memory that it is allowed to read.
- These flags registers must be restored upon exiting the assembly code if the
preserves_flagsoption is set:- x86
EFLAGS의 상태 플래그 (CF, PF, AF, ZF, SF, OF).- 부동 소수점 상태 워드 (전체).
MXCSR의 부동 소수점 예외 플래그 (PE, UE, OE, ZE, DE, IE).
- ARM
CPSR의 조건 플래그 (N, Z, C, V)CPSR의 포화(Saturation) 플래그 (Q)CPSR의 크거나 같음(Greater than or equal) 플래그 (GE).FPSCR의 조건 플래그 (N, Z, C, V)FPSCR의 포화(Saturation) 플래그 (QC)FPSCR의 부동 소수점 예외 플래그 (IDC, IXC, UFC, OFC, DZC, IOC).
- AArch64 및 Arm64EC
- 조건 플래그 (
NZCV레지스터). - 부동 소수점 상태 (
FPSR레지스터).
- 조건 플래그 (
- RISC-V
fcsr의 부동 소수점 예외 플래그 (fflags).- Vector extension state (
vtype,vl,vxsat, andvxrm).
- LoongArch
$fcc[0-7]의 부동 소수점 조건 플래그.
- PowerPC/PowerPC64
- Floating-point status and sticky bits in the
fpscr(any field other than DRN, VE, OE, UE, ZE, XE, NI, or RN). - Vector status and sticky bits in the
vscr(any field other than NJ).
- Floating-point status and sticky bits in the
- PowerPC SPE
- The sticky and status bits of the
spefscr(any field other than FINXE, FINVE, FDBZE, FUNFE, FOVFE, or FRMC).
- The sticky and status bits of the
- s390x
- 조건 코드 레지스터
cc.
- 조건 코드 레지스터
- x86
- On x86, the direction flag (DF in
EFLAGS) is clear on entry to the assembly code and must be clear on exit.- Behavior is undefined if the direction flag is set on exiting the assembly code.
- x86에서, 모든
st([0-7])레지스터들이out("st(0)") _, out("st(1)") _, ...와 같이 클로버된 것으로 표시되지 않는 한, x87 부동 소수점 레지스터 스택은 변경되지 않은 상태로 유지되어야 합니다.- If all x87 registers are clobbered then the x87 register stack is guaranteed to be empty upon entering the assembly code. Assembly code must ensure that the x87 register stack is also empty when exiting the assembly code.
#[cfg(target_arch = "x86_64")]
pub fn fadd(x: f64, y: f64) -> f64 {
let mut out = 0f64;
let mut top = 0u16;
// 전체 x87 스택을 클로버하면 x87로 복잡한 작업을 수행할 수 있습니다.
unsafe { core::arch::asm!(
"fld qword ptr [{x}]",
"fld qword ptr [{y}])",
"faddp",
"fstp qword ptr [{out}]",
"xor eax, eax",
"fstsw ax",
"shl eax, 11",
x = in(reg) &x,
y = in(reg) &y,
out = in(reg) &mut out,
out("st(0)") _, out("st(1)") _, out("st(2)") _, out("st(3)") _,
out("st(4)") _, out("st(5)") _, out("st(6)") _, out("st(7)") _,
out("eax") top
);}
assert_eq!(top & 0x7, 0);
out
}
pub fn main() {
#[cfg(target_arch = "x86_64")]{
assert_eq!(fadd(1.0, 1.0), 2.0);
}
}
- arm64ec에서는 함수를 호출할 때 적절한 펑크(thunks)가 있는 호출 검사기(call checkers) 가 필수입니다.
- The requirement of restoring the stack pointer and non-output registers to their original value only applies when exiting the assembly code.
- This means that assembly code that does not fall through and does not jump to any
labelblocks, even if not markednoreturn, doesn’t need to preserve these registers. - When returning to the assembly code of a different
asm!block than you entered (e.g. for context switching), these registers must contain the value they had upon entering theasm!block that you are exiting.- You cannot exit the assembly code of an
asm!block that has not been entered. Neither can you exit the assembly code of anasm!block whose assembly code has already been exited (without first entering it again). - 모든 타겟별 상태(예: 스레드 로컬 저장소, 스택 바운드)를 전환할 책임은 사용자에게 있습니다.
- 동일한 함수나 블록 내일지라도, 서로 다른 두
asm!블록 사이를 점프할 때는 그 문맥(context)이 잠재적으로 다를 수 있음을 고려하고 문맥 전환(context switching)을 수행해야 합니다. 두asm!블록 사이에서 특정 문맥 값(예: 현재 스택 포인터 또는 스택 포인터 아래의 임시 값)이 변하지 않은 상태로 유지될 것이라고 가정할 수 없습니다. - 접근할 수 있는 메모리 위치의 집합은 진입한
asm!블록과 나가는asm!블록에서 허용된 위치들의 교집합입니다.
- You cannot exit the assembly code of an
- This means that assembly code that does not fall through and does not jump to any
- 소스 코드상에서 인접한 두
asm!블록이 사이에 다른 코드가 없더라도, 바이너리 상에서 사이에 다른 명령어 없이 연속된 주소에 위치할 것이라고 가정할 수 없습니다.
asm!블록이 출력 바이너리에 정확히 한 번 나타날 것이라고 가정할 수 없습니다. 컴파일러는asm!블록을 포함하는 함수가 여러 곳에 인라인되는 경우와 같이asm!블록의 여러 복사본을 인스턴스화할 수 있습니다.
- x86에서, 인라인 어셈블리는 컴파일러가 생성한 명령어에 적용될 수 있는 명령어 접두사(예:
LOCK)로 끝나서는 안 됩니다.- 현재 컴파일러는 인라인 어셈블리가 컴파일되는 방식 때문에 이를 감지할 수 없지만, 향후에는 이를 포착하여 거부할 수 있습니다.
Note
As a general rule, the flags covered by
preserves_flagsare those which are not preserved when performing a function call.
Rules for naked inline assembly
To avoid undefined behavior, these rules must be followed when using function-scope inline assembly in naked functions (naked_asm!):
- Any registers not used for function inputs according to the calling convention and function signature will contain an undefined value on entry to the
naked_asm!block.- 인라인 어셈블리 문맥에서 “정의되지 않은 값“이란, 해당 레지스터가 아키텍처에서 허용하는 가능한 값들 중 하나를 (비결정적으로) 가질 수 있음을 의미합니다. 특히 이는 읽을 때마다 값이 달라질 수 있는 LLVM의
undef와는 다릅니다(어셈블리 코드에는 그러한 개념이 존재하지 않기 때문입니다).
- 인라인 어셈블리 문맥에서 “정의되지 않은 값“이란, 해당 레지스터가 아키텍처에서 허용하는 가능한 값들 중 하나를 (비결정적으로) 가질 수 있음을 의미합니다. 특히 이는 읽을 때마다 값이 달라질 수 있는 LLVM의
- All callee-saved registers must have the same value upon return as they had on entry.
- Caller-saved registers may be used freely.
- Behavior is undefined if execution falls through past the end of the assembly code.
- Every path through the assembly code is expected to terminate with a return instruction or to diverge.
- 어셈블리 코드가 읽고 쓸 수 있도록 허용된 메모리 위치의 집합은 FFI 함수에 허용된 것과 동일합니다.
- The compiler cannot assume that the instructions in the
naked_asm!block are the ones that will actually be executed.- This effectively means that the compiler must treat the
naked_asm!as a black box and only take the interface specification into account, not the instructions themselves. - 런타임 코드 패칭(patching)은 타겟별 메커니즘을 통해 허용됩니다.
- This effectively means that the compiler must treat the
- Unwinding out of a
naked_asm!block is allowed.- For correct behavior, the appropriate assembler directives that emit unwinding metadata must be used.
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
#[unsafe(naked)]
extern "sysv64-unwind" fn unwinding_naked() {
core::arch::naked_asm!(
// "CFI" here stands for "call frame information".
".cfi_startproc",
// The CFA (canonical frame address) is the value of `rsp`
// before the `call`, i.e. before the return address, `rip`,
// was pushed to `rsp`, so it's eight bytes higher in memory
// than `rsp` upon function entry (after `rip` has been
// pushed).
//
// This is the default, so we don't have to write it.
//".cfi_def_cfa rsp, 8",
//
// The traditional thing to do is to preserve the base
// pointer, so we'll do that.
"push rbp",
// Since we've now extended the stack downward by 8 bytes in
// memory, we need to adjust the offset to the CFA from `rsp`
// by another 8 bytes.
".cfi_adjust_cfa_offset 8",
// We also then annotate where we've stored the caller's value
// of `rbp`, relative to the CFA, so that when unwinding into
// the caller we can find it, in case we need it to calculate
// the caller's CFA relative to it.
//
// Here, we've stored the caller's `rbp` starting 16 bytes
// below the CFA. I.e., starting from the CFA, there's first
// the `rip` (which starts 8 bytes below the CFA and continues
// up to it), then there's the caller's `rbp` that we just
// pushed.
".cfi_offset rbp, -16",
// As is traditional, we set the base pointer to the value of
// the stack pointer. This way, the base pointer stays the
// same throughout the function body.
"mov rbp, rsp",
// We can now track the offset to the CFA from the base
// pointer. This means we don't need to make any further
// adjustments until the end, as we don't change `rbp`.
".cfi_def_cfa_register rbp",
// We can now call a function that may panic.
"call {f}",
// Upon return, we restore `rbp` in preparation for returning
// ourselves.
"pop rbp",
// Now that we've restored `rbp`, we must specify the offset
// to the CFA again in terms of `rsp`.
".cfi_def_cfa rsp, 8",
// Now we can return.
"ret",
".cfi_endproc",
f = sym may_panic,
)
}
extern "sysv64-unwind" fn may_panic() {
panic!("unwind");
}
}
}
Note
For more information on the
cfiassembler directives above, see these resources:
Correctness and validity
In addition to all of the previous rules, the string argument to asm! must ultimately become—after all other arguments are evaluated, formatting is performed, and operands are translated—assembly that is both syntactically correct and semantically valid for the target architecture. The formatting rules allow the compiler to generate assembly with correct syntax. Rules concerning operands permit valid translation of Rust operands into and out of the assembly code. Adherence to these rules is necessary, but not sufficient, for the final expanded assembly to be both correct and valid. For instance:
- 인자들이 포맷팅된 후 구문적으로 잘못된 위치에 배치될 수 있습니다.
- 명령어는 올바르게 작성되었으나 아키텍처적으로 유효하지 않은 피연산자가 제공될 수 있습니다.
- 아키텍처적으로 명시되지 않은 명령어가 명시되지 않은 코드로 어셈블될 수 있습니다.
- 각각은 정확하고 유효한 명령어 집합일지라도, 연달아 배치될 경우 정의되지 않은 동작을 초래할 수 있습니다.
결과적으로, 이러한 규칙들은 비포괄적(non-exhaustive) 입니다. 컴파일러는 초기 문자열이나 최종적으로 생성된 어셈블리의 정확성과 유효성을 검사할 의무가 없습니다. 어셈블러는 정확성과 유효성을 검사할 수도 있지만 반드시 그래야 하는 것은 아닙니다. asm! 을 사용할 때, 오타 하나만으로도 프로그램이 불안정해질 수 있으며, 어셈블리 규칙에는 수천 페이지 분량의 아키텍처 참조 매뉴얼이 포함될 수 있습니다. 프로그래머는 이 unsafe 기능을 호출하는 것이 컴파일러와 아키텍처 양쪽의 규칙을 위반하지 않을 책임을 지는 것임을 인지하고 적절한 주의를 기울여야 합니다.
Directives support
인라인 어셈블리는 다음과 같이 GNU AS와 LLVM 내부 어셈블러 양쪽에서 지원하는 지시어의 하위 집합을 지원합니다. 다른 지시어를 사용한 결과는 어셈블러에 따라 다르며(오류를 발생시키거나 그대로 수용될 수 있음).
If inline assembly includes any “stateful” directive that modifies how subsequent assembly is processed, the assembly code must undo the effects of any such directives before the inline assembly ends.
다음 지시어들은 어셈블러에 의해 지원됨이 보장됩니다:
.2byte.4byte.8byte.align.alt_entry.ascii.asciz.balign.balignl.balignw.bss.byte.comm.data.def.double.endef.equ.equiv.eqv.fill.float.global.globl.inst.insn.lcomm.long.octa.option.p2align.popsection.private_extern.pushsection.quad.scl.section.set.short.size.skip.sleb128.space.string.text.type.uleb128.word
#![allow(unused)]
fn main() {
#[cfg(target_arch = "x86_64")] {
let bytes: *const u8;
let len: usize;
unsafe {
core::arch::asm!(
"jmp 3f", "2: .ascii \"Hello World!\"",
"3: lea {bytes}, [2b+rip]",
"mov {len}, 12",
bytes = out(reg) bytes,
len = out(reg) len
);
}
let s = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(bytes, len)) };
assert_eq!(s, "Hello World!");
}
}
Target specific directive support
Dwarf unwinding
DWARF 언와인딩 정보를 지원하는 ELF 타겟에서 다음 지시어들이 지원됩니다:
.cfi_adjust_cfa_offset.cfi_def_cfa.cfi_def_cfa_offset.cfi_def_cfa_register.cfi_endproc.cfi_escape.cfi_lsda.cfi_offset.cfi_personality.cfi_register.cfi_rel_offset.cfi_remember_state.cfi_restore.cfi_restore_state.cfi_return_column.cfi_same_value.cfi_sections.cfi_signal_frame.cfi_startproc.cfi_undefined.cfi_window_save
Structured exception handling
구조화된 예외 처리를 사용하는 타겟에서는 다음의 추가 지시어들이 지원됨이 보장됩니다:
.seh_endproc.seh_endprologue.seh_proc.seh_pushreg.seh_savereg.seh_setframe.seh_stackalloc
x86 (32비트 및 64비트)
32비트 및 64비트 x86 타겟 모두에서 다음의 추가 지시어들이 지원됨이 보장됩니다:
.nops.code16.code32.code64
Use of .code16, .code32, and .code64 directives are only supported if the state is reset to the default before exiting the assembly code. 32-bit x86 uses .code32 by default, and x86_64 uses .code64 by default.
ARM (32비트)
ARM에서 다음의 추가 지시어들이 지원됨이 보장됩니다:
.even.fnstart.fnend.save.movsp.code.thumb.thumb_func
안전하지 않음
안전하지 않은(unsafe) 연산은 러스트의 정적 세만틱(static semantics)이 보장하는 메모리 안전성을 잠재적으로 위반할 수 있는 연산들입니다.
다음의 언어 레벨 기능들은 러스트의 안전한 하위 집합(safe subset)에서 사용될 수 없습니다:
- 원시 포인터(raw pointer) 를 역참조하는 것.
- 값을 할당하는 경우를 제외하고
유니온(union)의 필드에 접근하는 것.
- Calling an unsafe function.
- 동일한 기능을 활성화하는
target_feature속성이 없는 함수에서target_feature가 지정된 안전한 함수를 호출하는 것(attributes.codegen.target_feature.safety-restrictions 참조).
- 안전하지 않은 트레잇(unsafe trait) 을 구현하는 것.
- 아이템에 안전하지 않은 속성(unsafe attribute) 을 적용하는 것.
-
2024 에디션 이전에는 extern 블록을
unsafe없이 선언할 수 있었습니다. ↩
unsafe 키워드
The unsafe keyword is used to create or discharge the obligation to prove something safe. Specifically:
- It is used to mark code that defines extra safety conditions that must be upheld elsewhere.
- This includes
unsafe fn,unsafe static, andunsafe trait.
- This includes
- It is used to mark code that the programmer asserts satisfies safety conditions defined elsewhere.
- This includes
unsafe {},unsafe impl,unsafe fnwithoutunsafe_op_in_unsafe_fn,unsafe extern, and#[unsafe(attr)].
- This includes
다음은 각 경우에 대해 설명합니다. 몇 가지 예시는 키워드 문서 를 참조하십시오.
The unsafe keyword can occur in several different contexts:
- unsafe functions (
unsafe fn) - unsafe blocks (
unsafe {}) - unsafe traits (
unsafe trait) - unsafe trait implementations (
unsafe impl) - unsafe external blocks (
unsafe extern) - unsafe external statics (
unsafe static) - unsafe attributes (
#[unsafe(attr)])
안전하지 않은 함수 (unsafe fn)
안전하지 않은 함수는 모든 문맥에서 그리고/또는 모든 가능한 입력에 대해 안전하지 않은 함수입니다. 이러한 함수들은 모든 호출자가 준수해야 하지만 컴파일러가 확인하지 않는 요구 사항인 _추가 안전 조건 _을 갖는다고 말합니다. 예를 들어, get_unchecked 는 인덱스가 범위를 벗어나지 않아야 한다는 추가 안전 조건을 가집니다. 안전하지 않은 함수는 그러한 추가 안전 조건이 무엇인지 설명하는 문서를 동반해야 합니다.
이러한 함수는 unsafe 키워드를 앞에 붙여야 하며, unsafe 블록 내부 또는 unsafe_op_in_unsafe_fn 린트가 없는 unsafe fn 내부에서만 호출될 수 있습니다.
안전하지 않은 블록 (unsafe {})
코드 블록 앞에 unsafe 키워드를 붙여 안전하지 않음(Unsafety) 장에 정의된 안전하지 않은 동작들(예: 다른 안전하지 않은 함수 호출 또는 원시 포인터 역참조)의 사용을 허용할 수 있습니다.
기본적으로 안전하지 않은 함수의 본문은 안전하지 않은 블록으로 간주됩니다. 이는 unsafe_op_in_unsafe_fn 린트를 활성화하여 변경할 수 있습니다.
연산들을 unsafe 블록 안에 넣음으로써, 프로그래머는 해당 블록 내부의 모든 연산에 대한 추가 안전 조건을 충족하도록 조치를 취했음을 선언합니다.
안전하지 않은 블록은 안전하지 않은 함수와 논리적으로 대칭을 이룹니다. 안전하지 않은 함수가 호출자가 준수해야 하는 증명 책임(proof obligation)을 정의한다면, 안전하지 않은 블록은 블록 내부에서 호출된 함수나 연산의 모든 관련 증명 책임이 완료되었음을 나타냅니다. 증명 책임을 완료하는 방법은 많습니다. 예를 들어, 특정 속성이 확실히 참임을 보장하는 런타임 검사나 데이터 구조의 불변성(invariants)이 있을 수 있습니다. 또는 안전하지 않은 블록이 unsafe fn 내부에 있는 경우, 해당 블록은 그 함수의 증명 책임을 사용하여 블록 내부에서 발생하는 증명 책임을 완료할 수 있습니다.
안전하지 않은 블록은 외부 라이브러리를 래핑하거나, 하드웨어를 직접 사용하거나, 언어에 직접 존재하지 않는 기능을 구현할 때 사용됩니다. 예를 들어, 러스트는 메모리 안전한 동시성을 구현하는 데 필요한 언어적 기능을 제공하지만, 표준 라이브러리의 스레드 및 메시지 패싱 구현은 안전하지 않은 블록을 사용합니다.
러스트의 타입 시스템은 동적 안전성 요구 사항에 대한 보수적인 근사치이므로, 어떤 경우에는 안전한 코드를 사용하는 데 성능 비용이 발생합니다. 예를 들어, 이중 연결 리스트는 트리 구조가 아니며 안전한 코드에서는 참조 횟수 계산(RC) 포인터로만 표현될 수 있습니다. unsafe 블록을 사용하여 역방향 링크를 원시 포인터로 표현하면 참조 횟수 계산 없이 구현할 수 있습니다. (이 예제에 대한 더 심도 있는 탐구는 “Learn Rust With Entirely Too Many Linked Lists” 를 참조하십시오.)
안전하지 않은 트레잇 (unsafe trait)
안전하지 않은 트레잇은 트레잇의 구현체 가 준수해야 하는 추가 안전 조건이 함께 제공되는 트레잇입니다. 안전하지 않은 트레잇은 그러한 추가 안전 조건이 무엇인지 설명하는 문서를 동반해야 합니다.
그러한 트레잇은 unsafe 키워드를 앞에 붙여야 하며, 오직 unsafe impl 블록을 통해서만 구현될 수 있습니다.
안전하지 않은 트레잇 구현 (unsafe impl)
안전하지 않은 트레잇을 구현할 때, 구현체 앞에 unsafe 키워드를 붙여야 합니다. unsafe impl 을 작성함으로써, 프로그래머는 트레잇에서 요구하는 추가 안전 조건을 충족하도록 조치를 취했음을 선언합니다.
안전하지 않은 트레잇 구현은 안전하지 않은 트레잇과 논리적으로 대칭을 이룹니다. 안전하지 않은 트레잇이 구현체가 준수해야 하는 증명 책임을 정의한다면, 안전하지 않은 구현은 모든 관련 증명 책임이 완료되었음을 나타냅니다.
안전하지 않은 외부 블록 (unsafe extern)
외부 블록 을 선언하는 프로그래머는 내부에 포함된 아이템의 시그니처가 정확함을 보장해야 합니다. 그렇지 않으면 정의되지 않은 동작으로 이어질 수 있습니다. 이러한 의무가 충족되었음을 unsafe extern 을 작성하여 나타냅니다.
2024 Edition differences
Prior to edition 2024,
externblocks were allowed without being qualified asunsafe.
안전하지 않은 속성 (#[unsafe(attr)])
안전하지 않은 속성 은 속성을 사용할 때 준수해야 하는 추가 안전 조건이 있는 속성입니다. 컴파일러는 이러한 조건이 준수되었는지 확인할 수 없습니다. 조건이 충족되었음을 단언하기 위해, 이러한 속성들은 unsafe(..) 로 감싸야 합니다. 예를 들어, #[unsafe(no_mangle)] 과 같습니다.
정의되지 않은 동작으로 간주되는 경우
러스트 코드가 다음 목록의 동작 중 어느 하나라도 보인다면 잘못된 것입니다. 이는 unsafe 블록과 unsafe 함수 내부의 코드를 포함합니다. unsafe 는 단지 정의되지 않은 동작을 피하는 책임이 프로그래머에게 있음을 의미할 뿐이며, 러스트 프로그램이 정의되지 않은 동작을 결코 일으켜서는 안 된다는 사실을 바꾸지는 않습니다.
안전하지 않은 코드를 작성할 때, 그 코드와 상호 작용하는 모든 안전한 코드가 이러한 동작들을 유발할 수 없도록 보장하는 것은 프로그래머의 책임입니다. 모든 안전한 클라이언트에 대해 이 속성을 만족하는 안전하지 않은 코드를 건전하다(sound) 고 하며, 안전한 코드에 의해 오용되어 정의되지 않은 동작을 보일 수 있는 안전하지 않은 코드는 불건전하다(unsound) 고 합니다.
Warning
The following list is not exhaustive; it may grow or shrink. There is no formal model of Rust’s semantics for what is and is not allowed in unsafe code, so there may be more behavior considered unsafe. We also reserve the right to make some of the behavior in that list defined in the future. In other words, this list does not say that anything will definitely always be undefined in all future Rust version (but we might make such commitments for some list items in the future).
안전하지 않은 코드를 작성하기 전에 러스트노미콘(Rustonomicon) 을 읽어보시기 바랍니다.
- 데이터 경합 (Data races).
- 매달린 포인터(dangling pointer) 이거나 정렬되지 않은 포인터에 기반한 위치에 접근(로드 또는 스토어)하는 것.
- 범위 내 포인터 산술 연산(in-bounds pointer arithmetic) 의 요구 사항을 위반하는 위치 투영(place projection)을 수행하는 것. 위치 투영이란 필드 표현식, 튜플 인덱스 표현식, 또는 배열/슬라이스 인덱스 표현식 을 의미합니다.
-
Breaking the pointer aliasing rules. The exact aliasing rules are not determined yet, but here is an outline of the general principles:
&Tmust point to memory that is not mutated while they are live (except for data inside anUnsafeCell<U>), and&mut Tmust point to memory that is not read or written by any pointer not derived from the reference and that no other reference points to while they are live.Box<T>is treated similar to&'static mut Tfor the purpose of these rules. The exact liveness duration is not specified, but some bounds exist:- 참조의 경우, 생존 기간의 상한선은 대여 검사기(borrow checker)가 할당한 구문상 라이프타임입니다. 즉, 그 라이프타임보다 더 길게 살아있을 수 없습니다.
- Each time a reference or box is dereferenced or reborrowed, it is considered live.
- 참조나 박스가 함수로 전달되거나 함수에서 반환될 때마다, 그것은 살아있는 것으로 간주됩니다.
- 참조가(
Box가 아닌!) 함수로 전달될 때, 그것은 최소한 해당 함수 호출 동안은 살아있습니다. 이 역시&T가UnsafeCell<U>를 포함하는 경우는 예외입니다. 이 모든 사항은 이러한 타입의 값들이 복합 타입의 (중첩된) 필드로 전달될 때도 적용되지만, 포인터 간접 참조(indirections) 뒤에 있는 경우는 제외됩니다.
-
불변(immutable) 바이트를 수정하는 것. 상수 승격(const-promoted) 표현식을 통해 도달 가능한 모든 바이트는 불변이며,
'static으로 수명 연장(lifetime-extended) 된static및const초기화 식의 대여를 통해 도달 가능한 바이트들도 불변입니다. 불변 바인딩이나 불변static에 의해 소유된 바이트들은, 해당 바이트들이UnsafeCell<U>의 일부가 아닌 한 불변입니다.게다가, 공유 참조가 가리키는 바이트들은 다른 참조(공유 및 가변 모두)와
Box를 통한 전이적 참조를 포함하여 모두 불변입니다. 전이성에는 복합 타입의 필드에 저장된 참조들도 포함됩니다.수정이란 관련 바이트 중 어느 하나라도 겹치는 0바이트 이상의 모든 쓰기 연산을 의미합니다(해당 쓰기가 메모리 내용을 변경하지 않더라도 마찬가지입니다).
- 컴파일러 내장 함수(intrinsics)를 통해 정의되지 않은 동작을 유발하는 것.
- 현재 플랫폼이 지원하지 않는 플랫폼 기능을 사용하여 컴파일된 코드를 실행하는 것(
target_feature참조). 단, 플랫폼에서 이를 안전하다고 명시적으로 문서화한 경우는 제외합니다.
- Calling a function with the wrong call ABI, or unwinding past a stack frame that does not allow unwinding (e.g. by calling a
"C-unwind"function imported or transmuted as a"C"function or function pointer).
- 유효하지 않은 값 을 생성하는 것. 값을 “생성“한다는 것은 값이 장소(place)에 할당되거나 장소로부터 읽힐 때, 함수/기본 연산으로 전달되거나 함수/기본 연산으로부터 반환될 때마다 발생합니다.
- 인라인 어셈블리의 잘못된 사용. 자세한 내용은 인라인 어셈블리를 사용하는 코드를 작성할 때 따라야 할 규칙 을 참조하십시오.
- Violating assumptions of the Rust runtime. Most assumptions of the Rust runtime are currently not explicitly documented.
- For assumptions specifically related to unwinding, see the panic documentation.
- The runtime assumes that a Rust stack frame is not deallocated without executing destructors for local variables owned by the stack frame. This assumption can be violated by C functions like
longjmp.
Note
Undefined behavior affects the entire program. For example, calling a function in C that exhibits undefined behavior of C means your entire program contains undefined behaviour that can also affect the Rust code. And vice versa, undefined behavior in Rust can cause adverse affects on code executed by any FFI calls to other languages.
가리키는 바이트 (Pointed-to bytes)
포인터나 참조가 “가리키는” 바이트 범위는 포인터 값과 피지시체(pointee) 타입의 크기(size_of_val 사용)에 의해 결정됩니다.
잘못 정렬된 포인터에 기반한 장소 (Places based on misaligned pointers)
A place is said to be “based on a misaligned pointer” if the last * projection during place computation was performed on a pointer that was not aligned for its type. (If there is no * projection in the place expression, then this is accessing the field of a local or static and rustc will guarantee proper alignment. If there are multiple * projections, then each of them incurs a load of the pointer-to-be-dereferenced itself from memory, and each of these loads is subject to the alignment constraint. Note that some * projections can be omitted in surface Rust syntax due to automatic dereferencing; we are considering the fully expanded place expression here.)
예를 들어, ptr 의 타입이 *const S 이고 S 의 정렬(alignment)이 8이라면, ptr 은 반드시 8로 정렬되어야 합니다. 그렇지 않으면 (*ptr).f 는 “잘못 정렬된 포인터에 기반한” 것이 됩니다. 이는 필드 f 의 타입이 u8(즉, 정렬이 1인 타입)인 경우에도 마찬가지입니다. 다시 말해, 정렬 요구 사항은 접근하려는 필드의 타입이 아니라 역참조된 포인터의 타입에서 비롯됩니다.
Note that a place based on a misaligned pointer only leads to undefined behavior when it is loaded from or stored to.
그러한 장소에 대한 &raw const/&raw mut 는 허용됩니다.
장소에 대한 &/&mut 는 필드 타입의 정렬을 요구하며(그렇지 않으면 프로그램이 “유효하지 않은 값을 생성“하게 됨), 이는 일반적으로 정렬된 포인터에 기반해야 한다는 요구 사항보다 덜 제한적입니다.
필드 타입이 자신을 포함하는 타입보다 더 엄격하게 정렬되어 있을 수 있는 경우(예: repr(packed)), 참조를 취하면 컴파일러 오류가 발생합니다. 이는 정렬된 포인터에 기반하는 것이 새로운 참조가 정렬되도록 보장하는 데 항상 충분하지만, 항상 필수적인 것은 아님을 의미합니다.
매달린 포인터 (Dangling pointers)
참조/포인터가 가리키는 모든 바이트가 동일한 살아있는(live) 할당의 일부가 아닌 경우(특히 모든 바이트는 어떤 할당의 일부여야 함), 해당 참조/포인터는 “매달려(dangling)” 있다고 합니다.
크기가 0인 경우, 포인터는 (비록 널 포인터일지라도) 결코 “매달려” 있지 않은 것으로 간주됩니다.
동적 크기 타입(슬라이스 및 문자열 등)은 전체 범위를 가리키므로, 길이 메타데이터가 결코 너무 크지 않도록 하는 것이 중요합니다.
특히, 러스트 값의 동적 크기(size_of_val 에 의해 결정됨)는 결코 isize::MAX 를 초과해서는 안 됩니다. 단일 할당이 isize::MAX 보다 큰 것은 불가능하기 때문입니다.
유효하지 않은 값 (Invalid values)
러스트 컴파일러는 프로그램 실행 중에 생성된 모든 값이 “유효하다“고 가정합니다. 따라서 유효하지 않은 값을 생성하는 것은 즉시 정의되지 않은 동작(UB)으로 이어집니다.
값이 유효한지 여부는 타입에 따라 다릅니다:
bool값은false(0) 또는true(1)여야 합니다.
fn포인터 값은 널(null)이 아니어야 합니다.
char값은 써로게이트(surrogate, 즉0xD800..=0xDFFF범위 내의 값)가 아니어야 하며,char::MAX보다 작거나 같아야 합니다.
!값은 결코 존재해서는 안 됩니다.
- An integer (
i*/u*), floating point value (f*), or raw pointer must be initialized, i.e., must not be obtained from uninitialized memory.
str값은[u8]과 같이 취급됩니다. 즉, 반드시 초기화되어야 합니다.
- 열거형(
enum)은 유효한 판별자(discriminant)를 가져야 하며, 해당 판별자가 나타내는 변형(variant)의 모든 필드는 각각의 타입에 대해 유효해야 합니다.
- 구조체(
struct), 튜플, 배열은 모든 필드/요소가 각각의 타입에 대해 유효할 것을 요구합니다.
- 유니온(
union)의 경우, 정확한 유효성 요구 사항은 아직 결정되지 않았습니다. 분명히, 안전한 코드만으로 생성할 수 있는 모든 값은 유효합니다. 만약 유니온이 크기가 0인 필드를 가지고 있다면, 가능한 모든 값이 유효합니다. 더 자세한 내용은 여전히 논의 중 입니다.
- 참조 또는
Box<T>는 정렬되어 있어야 하고 널이 아니어야 하며, 매달려 있어서도 안 됩니다. 또한 유효한 값을 가리켜야 합니다(동적 크기 타입의 경우, 메타데이터에 의해 결정된 피지시체의 실제 동적 타입을 사용). 마지막 지점(유효한 값을 가리켜야 한다는 것)은 여전히 논의의 여지가 있음에 유의하십시오.
- 와이드 참조(wide reference),
Box<T>, 또는 원시 포인터의 메타데이터는 크기가 정해지지 않은 꼬리(unsized tail)의 타입과 일치해야 합니다:dyn Trait메타데이터는 컴파일러가 생성한Trait용 vtable에 대한 포인터여야 합니다. (원시 포인터의 경우, 이 요구 사항은 여전히 논의 중입니다.)- 슬라이스(
[T]) 메타데이터는 유효한usize여야 합니다. 더 나아가, 와이드 참조 및Box<T>의 경우, 슬라이스 메타데이터가 가리키는 값의 전체 크기를isize::MAX보다 크게 만든다면 유효하지 않습니다.
-
만약 타입이 유효한 값의 커스텀 범위를 가지고 있다면, 유효한 값은 반드시 그 범위 내에 있어야 합니다. 표준 라이브러리에서는
NonNull<T>와NonZero<T>가 이에 해당합니다.Note
rustcachieves this with the unstablerustc_layout_scalar_valid_range_*attributes.
-
In const contexts: In addition to what is described above, further provenance-related requirements apply during const evaluation. Any value that holds pure integer data (the
i*/u*/f*types as well asboolandchar, enum discriminants, and slice metadata) must not carry any provenance. Any value that holds pointer data (references, raw pointers, function pointers, anddyn Traitmetadata) must either carry no provenance, or all bytes must be fragments of the same original pointer value in the correct order.This implies that transmuting or otherwise reinterpreting a pointer (reference, raw pointer, or function pointer) into a non-pointer type (such as integers) is undefined behavior if the pointer had provenance.
Example
All of the following are UB:
#![allow(unused)] fn main() { use core::mem::MaybeUninit; use core::ptr; // We cannot reinterpret a pointer with provenance as an integer, // as then the bytes of the integer will have provenance. const _: usize = { let ptr = &0; unsafe { (&raw const ptr as *const usize).read() } }; // We cannot rearrange the bytes of a pointer with provenance and // then interpret them as a reference, as then a value holding // pointer data will have pointer fragments in the wrong order. const _: &i32 = { let mut ptr = &0; let ptr_bytes = &raw mut ptr as *mut MaybeUninit::<u8>; unsafe { ptr::swap(ptr_bytes.add(1), ptr_bytes.add(2)) }; ptr }; }
참고: 초기화되지 않은 메모리는 제한된 유효 값 집합을 가진 모든 타입에 대해 암시적으로 유효하지 않습니다. 다시 말해, 초기화되지 않은 메모리를 읽는 것이 허용되는 유일한 경우는 union 내부와 “패딩(padding)”(타입의 필드 사이의 간격)뿐입니다.
unsafe 로 간주되지 않는 동작
러스트 컴파일러는 다음의 동작들을 안전하지 않다(unsafe) 고 간주하지 않습니다. 비록 프로그래머가 이를 바람직하지 않거나, 예상치 못했거나, 잘못된 것으로 생각할지라도 말입니다.
- 데드락 (Deadlocks)
- 메모리 및 기타 리소스 누수 (Leaks)
- 소멸자(destructor)를 호출하지 않고 종료하는 것
- 포인터 누수를 통해 무작위화된 베이스 주소를 노출하는 것
정수 오버플로 (Integer overflow)
프로그램에 산술 오버플로가 포함되어 있다면, 이는 프로그래머의 실수입니다. 이어지는 논의에서, 우리는 산술 오버플로와 래핑(wrapping) 산술 연산을 구분합니다. 전자는 잘못된 것이고, 후자는 의도된 것입니다.
프로그래머가 debug_assert! 단언을 활성화한 경우(예: 최적화되지 않은 빌드), 구현체는 오버플로 시 panic 을 일으키는 동적 검사를 삽입해야 합니다. 다른 종류의 빌드에서는 구현체의 재량에 따라 오버플로 시 panic 이 발생하거나 조용히 값이 래핑될 수 있습니다.
암시적으로 래핑되는 오버플로의 경우, 구현체는 2의 보수 오버플로 관례를 사용하여 잘 정의된(여전히 오류로 간주될지라도) 결과를 제공해야 합니다.
정수 타입들은 프로그래머가 명시적으로 래핑 산술 연산을 수행할 수 있도록 내재적 메서드들을 제공합니다. 예를 들어, i32::wrapping_add 는 2의 보수 래핑 덧셈을 제공합니다.
표준 라이브러리는 또한 T 에 대한 모든 표준 산술 연산이 래핑 세만틱을 갖도록 보장하는 Wrapping<T> 뉴타입(newtype)을 제공합니다.
오류 조건, 근거, 그리고 정수 오버플로에 대한 더 자세한 내용은 RFC 560 을 참조하십시오.
논리 오류 (Logic errors)
안전한 코드는 컴파일 타임이나 런타임에 확인할 수 없는 추가적인 논리적 제약을 부과할 수 있습니다. 프로그램이 이러한 제약을 위반할 경우, 동작은 명시되지 않을 수 있으나 정의되지 않은 동작으로 이어지지는 않습니다. 여기에는 패닉, 잘못된 결과, 중단(abort), 그리고 종료되지 않음(non-termination) 등이 포함될 수 있습니다. 동작은 실행, 빌드, 또는 빌드 종류에 따라 달라질 수도 있습니다.
예를 들어, Hash 와 Eq 를 모두 구현할 때는 동일하다고 간주되는 값들이 동일한 해시 값을 가져야 한다는 요구 사항이 있습니다. 또 다른 예로 BinaryHeap, BTreeMap, BTreeSet, HashMap, HashSet 과 같은 데이터 구조들은 데이터 구조에 포함된 동안 키(key)의 수정에 대한 제약 조건을 기술합니다. 이러한 제약 조건을 위반하는 것은 안전하지 않은(unsafe) 것으로 간주되지는 않지만, 프로그램은 오류가 있는 것으로 간주되며 그 동작은 예측할 수 없게 됩니다.
상수 평가 (Constant evaluation)
상수 평가는 컴파일 중에 표현식 의 결과를 계산하는 과정입니다. 모든 표현식 중 일부 하위 집합만이 컴파일 타임에 평가될 수 있습니다.
상수 표현식
상수 표현식이라고 불리는 특정 형태의 표현식들은 컴파일 타임에 평가될 수 있습니다.
Expressions in a const context must be constant expressions.
Expressions in const contexts are always evaluated at compile time.
Outside of const contexts, constant expressions may be, but are not guaranteed to be, evaluated at compile time.
범위를 벗어난 배열 인덱싱 이나 오버플로 와 같은 동작들은, 값이 컴파일 타임에 평가되어야 하는 경우(즉, 상수 컨텍스트인 경우) 컴파일러 오류가 됩니다. 그렇지 않으면 이러한 동작들은 경고로 처리되지만, 런타임에 패닉이 발생할 가능성이 높습니다.
다음 표현식들은 피연산자 또한 상수 표현식이고 어떠한 Drop::drop 호출도 일으키지 않는 한 상수 표현식입니다.
- 리터럴.
-
다음 제약 사항이 있는 정적 변수(statics) 에 대한 경로:
static아이템에 쓰는 것은 어떠한 상수 평가 컨텍스트에서도 허용되지 않습니다.extern정적 변수로부터 읽는 것은 어떠한 상수 평가 컨텍스트에서도 허용되지 않습니다.- 평가가
static아이템의 초기화 식 내에서 수행되는 것이 아니라면, 가변static으로부터 읽는 것은 허용되지 않습니다. 가변static은static mut아이템이거나, 내부 가변성(interior-mutable) 타입을 가진static아이템을 의미합니다. 이러한 요구 사항들은 상수가 평가될 때만 확인됩니다. 다시 말해, 이러한 접근이 상수 컨텍스트에서 구문상으로 나타나는 것은 실제로 실행되지 않는 한 허용됩니다.
unsafe및const블록을 포함한 블록 표현식.
- Array and slice indexing expressions, where the index is a
usize.
- 환경으로부터 변수를 캡처하지 않는 클로저 표현식.
- 정수 및 부동 소수점 타입,
bool,char에 사용되는 내장 부정, 산술, 논리, 비교 또는 지연 평가 불리언(lazy boolean) 연산자.
-
All forms of borrows, including raw borrows, except borrows of expressions whose temporary scopes would be extended (see temporary lifetime extension) to the end of the program and which are either:
- Mutable borrows.
- Shared borrows of expressions that result in values with interior mutability.
#![allow(unused)] fn main() { // Due to being in tail position, this borrow extends the scope of the // temporary to the end of the program. Since the borrow is mutable, // this is not allowed in a const expression. const C: &u8 = &mut 0; // ERROR not allowed }#![allow(unused)] fn main() { // Const blocks are similar to initializers of `const` items. let _: &u8 = const { &mut 0 }; // ERROR not allowed }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // This is not allowed as 1) the temporary scope is extended to the // end of the program and 2) the temporary has interior mutability. const C: &AtomicU8 = &AtomicU8::new(0); // ERROR not allowed }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // As above. let _: &_ = const { &AtomicU8::new(0) }; // ERROR not allowed }#![allow(unused)] fn main() { #![allow(static_mut_refs)] // Even though this borrow is mutable, it's not of a temporary, so // this is allowed. const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; // OK }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // Even though this borrow is of a value with interior mutability, // it's not of a temporary, so this is allowed. const C: &AtomicU8 = { static S: AtomicU8 = AtomicU8::new(0); &S // OK }; }#![allow(unused)] fn main() { use core::sync::atomic::AtomicU8; // This shared borrow of an interior mutable temporary is allowed // because its scope is not extended. const C: () = { _ = &AtomicU8::new(0); }; // OK }#![allow(unused)] fn main() { // Even though the borrow is mutable and the temporary lives to the // end of the program due to promotion, this is allowed because the // borrow is not in tail position and so the scope of the temporary // is not extended via temporary lifetime extension. const C: () = { let _: &'static mut [u8] = &mut []; }; // OK // ~~ // Promoted temporary. }Note
In other words — to focus on what’s allowed rather than what’s not allowed — shared borrows of interior mutable data and mutable borrows are only allowed in a const context when the borrowed place expression is transient, indirect, or static.
A place expression is transient if it is a variable local to the current const context or an expression whose temporary scope is contained inside the current const context.
#![allow(unused)] fn main() { // The borrow is of a variable local to the initializer, therefore // this place expression is transient. const C: () = { let mut x = 0; _ = &mut x; }; }#![allow(unused)] fn main() { // The borrow is of a temporary whose scope has not been extended, // therefore this place expression is transient. const C: () = { _ = &mut 0u8; }; }#![allow(unused)] fn main() { // When a temporary is promoted but not lifetime extended, its // place expression is still treated as transient. const C: () = { let _: &'static mut [u8] = &mut []; }; }A place expression is indirect if it is a dereference expression.
#![allow(unused)] fn main() { const C: () = { _ = &mut *(&mut 0); }; }A place expression is static if it is a
staticitem.#![allow(unused)] fn main() { #![allow(static_mut_refs)] const C: &u8 = unsafe { static mut S: u8 = 0; &mut S }; }Note
One surprising consequence of these rules is that we allow this,
#![allow(unused)] fn main() { const C: &[u8] = { let x: &mut [u8] = &mut []; x }; // OK // ~~~~~~~ // Empty arrays are promoted even behind mutable borrows. }but we disallow this similar code:
#![allow(unused)] fn main() { const C: &[u8] = &mut []; // 오류 // ~~~~~~~ // Tail expression. }The difference between these is that, in the first, the empty array is promoted but its scope does not undergo temporary lifetime extension, so we consider the place expression to be transient (even though after promotion the place indeed lives to the end of the program). In the second, the scope of the empty array temporary does undergo lifetime extension, and so it is rejected due to being a mutable borrow of a lifetime-extended temporary (and therefore borrowing a non-transient place expression).
The effect is surprising because temporary lifetime extension, in this case, causes less code to compile than would without it.
See issue #143129 for more details.
-
#![allow(unused)] fn main() { use core::cell::UnsafeCell; const _: u8 = unsafe { let x: *mut u8 = &raw mut *&mut 0; // ^^^^^^^ // Dereference of mutable reference. *x = 1; // Dereference of mutable pointer. *(x as *const u8) // Dereference of constant pointer. }; const _: u8 = unsafe { let x = &UnsafeCell::new(0); *x.get() = 1; // Mutation of interior mutable value. *x.get() }; }
- 그룹화된(Grouped) 표현식.
- 다음 경우를 제외한 캐스트 표현식:
- 포인터를 주소로 변환하는 캐스트 및
- 함수 포인터를 주소로 변환하는 캐스트.
- 상수 함수(const functions) 및 상수 메서드 호출.
상수 컨텍스트 (Const context)
상수 컨텍스트 는 다음 중 하나를 의미합니다:
Array type length expressions, array repeat length expressions, and const generic arguments are restricted in their use of outer generic parameters: such an expression must either be a single const generic parameter, or an expression that does not reference any generic parameters.
const 함수
A const function is a function that can be called from a const context. It is defined with the const qualifier, and also includes tuple struct and tuple enum variant constructors.
Example
#![allow(unused)] fn main() { const fn square(x: i32) -> i32 { x * x } const VALUE: i32 = square(12); }
When called from a const context, a const function is interpreted by the compiler at compile time. The interpretation happens in the environment of the compilation target and not the host. So usize is 32 bits if you are compiling against a 32 bit system, irrelevant of whether you are building on a 64 bit or a 32 bit system.
When a const function is called from outside a const context, it behaves the same as if it did not have the const qualifier.
The body of a const function may only use constant expressions.
Const functions are not allowed to be async.
The types of a const function’s parameters and return type are restricted to those that are compatible with a const context.
Application binary interface (ABI)
이 섹션은 크레이트의 컴파일된 출력물의 ABI에 영향을 주는 기능들을 설명합니다.
함수를 내보내기 위해 ABI를 지정하는 방법에 대한 정보는 _외부 함수(extern functions)_를 참조하십시오. 외부 라이브러리를 링크하기 위해 ABI를 지정하는 방법에 대한 정보는 _외부 블록(external blocks)_을 참조하십시오.
used 속성
used 속성 은 오직 static 아이템 에만 적용될 수 있습니다. 이 속성 은 해당 변수가 크레이트의 다른 아이템에 의해 사용되거나 참조되지 않더라도, 출력 목적 파일(.o, .rlib 등, 최종 바이너리 제외)에 변수를 강제로 유지하도록 컴파일러에 명령합니다. 하지만 링커는 여전히 이러한 아이템을 제거할 수 있습니다.
아래는 컴파일러가 어떤 조건 하에서 static 아이템을 출력 목적 파일에 유지하는지 보여주는 예제입니다.
#![allow(unused)]
fn main() {
// foo.rs
// 이는 `#[used]` 때문에 유지됩니다:
#[used]
static FOO: u32 = 0;
// 이는 사용되지 않으므로 제거 가능합니다:
#[allow(dead_code)]
static BAR: u32 = 0;
// 이는 공개적으로 도달 가능하므로 유지됩니다:
pub static BAZ: u32 = 0;
// 이는 공개적으로 도달 가능한 함수에 의해 참조되므로 유지됩니다:
static QUUX: u32 = 0;
pub fn quux() -> &'static u32 {
&QUUX
}
// 이는 비공개이며 사용되지 않는(dead) 함수에 의해 참조되므로 제거 가능합니다:
static CORGE: u32 = 0;
#[allow(dead_code)]
fn corge() -> &'static u32 {
&CORGE
}
}
$ rustc -O --emit=obj --crate-type=rlib foo.rs
$ nm -C foo.o
0000000000000000 R foo::BAZ
0000000000000000 r foo::FOO
0000000000000000 R foo::QUUX
0000000000000000 T foo::quux
no_mangle 속성
no_mangle 속성 은 표준 심볼 이름 맹글링(mangling)을 비활성화하기 위해 모든 아이템 에 사용될 수 있습니다. 해당 아이템의 심볼은 아이템의 이름과 동일한 식별자가 됩니다.
또한, used 속성 과 유사하게, 해당 아이템은 생성된 라이브러리나 목적 파일로부터 공개적으로 내보내집니다.
맹글링되지 않은 심볼은 동일한 이름을 가진 다른 심볼(또는 잘 알려진 심볼)과 충돌하여 정의되지 않은 동작을 초래할 수 있으므로, 이 속성은 안전하지 않습니다(unsafe).
#![allow(unused)]
fn main() {
#[unsafe(no_mangle)]
extern "C" fn foo() {}
}
2024 Edition differences
Before the 2024 edition it is allowed to use the
no_mangleattribute without theunsafequalification.
link_section 속성
The link_section attribute specifies the section of the object file that a function or static’s content will be placed into.
The link_section attribute uses the MetaNameValueStr syntax to specify the section name.
#![allow(unused)]
fn main() {
#[unsafe(no_mangle)]
#[unsafe(link_section = ".example_section")]
pub static VAR1: u32 = 1;
}
이 속성은 가변 데이터를 읽기 전용 영역에 배치하는 것과 같이, 데이터와 코드를 의도하지 않은 메모리 섹션에 배치할 수 있게 하므로 안전하지 않습니다(unsafe).
2024 Edition differences
Before the 2024 edition it is allowed to use the
link_sectionattribute without theunsafequalification.
export_name 속성
The export_name attribute specifies the name of the symbol that will be exported on a function or static.
The export_name attribute uses the MetaNameValueStr syntax to specify the symbol name.
#![allow(unused)]
fn main() {
#[unsafe(export_name = "exported_symbol_name")]
pub fn name_in_rust() { }
}
커스텀 이름을 가진 심볼은 동일한 이름을 가진 다른 심볼(또는 잘 알려진 심볼)과 충돌하여 정의되지 않은 동작을 초래할 수 있으므로, 이 속성은 안전하지 않습니다(unsafe).
2024 Edition differences
Before the 2024 edition it is allowed to use the
export_nameattribute without theunsafequalification.
러스트 런타임
이 섹션은 러스트 런타임의 일부 측면을 정의하는 기능들을 설명합니다.
global_allocator 속성
The global_allocator attribute selects a memory allocator.
Example
#![allow(unused)] fn main() { use core::alloc::{GlobalAlloc, Layout}; use std::alloc::System; struct MyAllocator; unsafe impl GlobalAlloc for MyAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { unsafe { System.alloc(layout) } } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { unsafe { System.dealloc(ptr, layout) } } } #[global_allocator] static GLOBAL: MyAllocator = MyAllocator; }
The global_allocator attribute uses the MetaWord syntax.
The global_allocator attribute may only be applied to a static item whose type implements the GlobalAlloc trait.
The global_allocator attribute may only be used once on an item.
The global_allocator attribute may only be used once in the crate graph.
The global_allocator attribute is exported from the standard library prelude.
windows_subsystem 속성
The windows_subsystem attribute sets the subsystem when linking on a Windows target.
Example
#![allow(unused)] #![windows_subsystem = "windows"] fn main() { }
The windows_subsystem attribute uses the MetaNameValueStr syntax. Accepted values are "console" and "windows".
The windows_subsystem attribute may only be applied to the crate root.
Only the first use of windows_subsystem has effect.
Note
rustclints against any use following the first. This may become an error in the future.
The windows_subsystem attribute is ignored on non-Windows targets and non-bin crate types.
The "console" subsystem is the default. If a console process is run from an existing console then it will be attached to that console; otherwise a new console window will be created.
The "windows" subsystem will run detached from any existing console.
Note
The
"windows"subsystem is commonly used by GUI applications that do not want to display a console window on startup.
부록
Grammar summary
The following is a summary of the grammar production rules. For details on the syntax of this grammar, see notation.grammar.syntax.
Macros summary
Syntax
MacroRulesDefinition →
macro_rules ! IDENTIFIER MacroRulesDef
MacroRulesDef →
( MacroRules ) ;
| [ MacroRules ] ;
| { MacroRules }
MacroRules →
MacroRule ( ; MacroRule )* ;?
MacroRule →
MacroMatcher => MacroTranscriber
MacroMatcher →
( MacroMatch* )
| [ MacroMatch* ]
| { MacroMatch* }
MacroMatch →
Tokenexcept $ and delimiters
| MacroMatcher
| $ ( IDENTIFIER_OR_KEYWORDexcept crate | RAW_IDENTIFIER ) : MacroFragSpec
| $ ( MacroMatch+ ) MacroRepSep? MacroRepOp
MacroFragSpec →
block | expr | expr_2021 | ident | item | lifetime | literal
| meta | pat | pat_param | path | stmt | tt | ty | vis
MacroRepSep → Tokenexcept delimiters and MacroRepOp
MacroRepOp → * | + | ?
MacroTranscriber → DelimTokenTree
MacroInvocation →
SimplePath ! DelimTokenTree
DelimTokenTree →
( TokenTree* )
| [ TokenTree* ]
| { TokenTree* }
TokenTree →
Tokenexcept delimiters | DelimTokenTree
MacroInvocationSemi →
SimplePath ! ( TokenTree* ) ;
| SimplePath ! [ TokenTree* ] ;
| SimplePath ! { TokenTree* }
Attributes summary
Syntax
ProcMacroDeriveAttribute →
proc_macro_derive ( DeriveMacroName ( , DeriveMacroAttributes )? ,? )
DeriveMacroAttributes →
attributes ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )
InlineAttribute →
inline ( always )
| inline ( never )
| inline
CollapseDebuginfoAttribute → collapse_debuginfo ( CollapseDebuginfoOption )
CollapseDebuginfoOption →
yes
| no
| external
InnerAttribute → # ! [ Attr ]
OuterAttribute → # [ Attr ]
Attr →
SimplePath AttrInput?
| unsafe ( SimplePath AttrInput? )
AttrInput →
DelimTokenTree
| = Expression
MetaItem →
SimplePath
| SimplePath = Expression
| SimplePath ( MetaSeq? )
MetaSeq →
MetaItemInner ( , MetaItemInner )* ,?
MetaItemInner →
MetaItem
| Expression
MetaNameValueStr →
IDENTIFIER = ( STRING_LITERAL | RAW_STRING_LITERAL )
MetaListPaths →
IDENTIFIER ( ( SimplePath ( , SimplePath )* ,? )? )
MetaListIdents →
IDENTIFIER ( ( IDENTIFIER ( , IDENTIFIER )* ,? )? )
MetaListNameValueStr →
IDENTIFIER ( ( MetaNameValueStr ( , MetaNameValueStr )* ,? )? )
Miscellaneous summary
Syntax
TypeParamBounds → TypeParamBound ( + TypeParamBound )* +?
TypeParamBound → Lifetime | TraitBound | UseBound
TraitBound →
( ? | ForLifetimes )? TypePath
| ( ( ? | ForLifetimes )? TypePath )
LifetimeBounds → ( Lifetime + )* Lifetime?
Lifetime →
LIFETIME_OR_LABEL
| ‘static
| ’_
UseBound → use UseBoundGenericArgs
UseBoundGenericArgs →
< >
| < ( UseBoundGenericArg , )* UseBoundGenericArg ,? >
UseBoundGenericArg →
Lifetime
| IDENTIFIER
| Self
ForLifetimes → for GenericParams
Assembly summary
Syntax
AsmArgs → AsmAttrFormatString ( , AsmAttrFormatString )* ( , AsmAttrOperand )* ,?
FormatString → STRING_LITERAL | RAW_STRING_LITERAL | MacroInvocation
AsmAttrFormatString → ( OuterAttribute )* FormatString
AsmOperand →
ClobberAbi
| AsmOptions
| RegOperand
AsmAttrOperand → ( OuterAttribute )* AsmOperand
ClobberAbi → clobber_abi ( Abi ( , Abi )* ,? )
AsmOptions →
options ( ( AsmOption ( , AsmOption )* ,? )? )
AsmOption →
pure
| nomem
| readonly
| preserves_flags
| noreturn
| nostack
| att_syntax
| raw
RegOperand → ( ParamName = )?
(
DirSpec ( RegSpec ) Expression
| DualDirSpec ( RegSpec ) DualDirSpecExpression
| sym PathExpression
| const Expression
| label { Statements? }
)
ParamName → IDENTIFIER_OR_KEYWORD | RAW_IDENTIFIER
DualDirSpecExpression →
Expression
| Expression => Expression
RegSpec → RegisterClass | ExplicitRegister
RegisterClass → IDENTIFIER_OR_KEYWORD
ExplicitRegister → STRING_LITERAL
DirSpec →
in
| out
| lateout
DualDirSpec →
inout
| inlateout
Items summary
Syntax
Visibility →
pub
| pub ( crate )
| pub ( self )
| pub ( super )
| pub ( in SimplePath )
Crate →
InnerAttribute*
Item*
Trait →
unsafe? trait IDENTIFIER GenericParams? ( : TypeParamBounds? )? WhereClause?
{
InnerAttribute*
AssociatedItem*
}
Implementation → InherentImpl | TraitImpl
InherentImpl →
impl GenericParams? Type WhereClause? {
InnerAttribute*
AssociatedItem*
}
TraitImpl →
unsafe? impl GenericParams? !? TypePath for Type
WhereClause?
{
InnerAttribute*
AssociatedItem*
}
ExternBlock →
unsafe? extern Abi? {
InnerAttribute*
ExternalItem*
}
ExternalItem →
OuterAttribute* (
MacroInvocationSemi
| Visibility? StaticItem
| Visibility? Function
)
Enumeration →
enum IDENTIFIER GenericParams? WhereClause? { EnumVariants? }
EnumVariants → EnumVariant ( , EnumVariant )* ,?
EnumVariant →
OuterAttribute* Visibility?
IDENTIFIER ( EnumVariantTuple | EnumVariantStruct )? EnumVariantDiscriminant?
EnumVariantTuple → ( TupleFields? )
EnumVariantStruct → { StructFields? }
EnumVariantDiscriminant → = Expression
ExternCrate → extern crate CrateRef AsClause? ;
CrateRef → IDENTIFIER | self
AsClause → as ( IDENTIFIER | _ )
TypeAlias →
type IDENTIFIER GenericParams? ( : TypeParamBounds )?
WhereClause?
( = Type WhereClause? )? ;
Union →
union IDENTIFIER GenericParams? WhereClause? { StructFields? }
GenericParams → < ( GenericParam ( , GenericParam )* ,? )? >
GenericParam → OuterAttribute* ( LifetimeParam | TypeParam | ConstParam )
LifetimeParam → Lifetime ( : LifetimeBounds )?
TypeParam → IDENTIFIER ( : TypeParamBounds? )? ( = Type )?
ConstParam →
const IDENTIFIER : Type
( = ( BlockExpression | IDENTIFIER | -? LiteralExpression ) )?
WhereClause → where ( WhereClauseItem , )* WhereClauseItem?
WhereClauseItem →
LifetimeWhereClauseItem
| TypeBoundWhereClauseItem
LifetimeWhereClauseItem → Lifetime : LifetimeBounds
TypeBoundWhereClauseItem → ForLifetimes? Type : TypeParamBounds?
Function →
FunctionQualifiers fn IDENTIFIER GenericParams?
( FunctionParameters? )
FunctionReturnType? WhereClause?
( BlockExpression | ; )
FunctionQualifiers → const? async? ItemSafety? ( extern Abi? )?
ItemSafety → safe | unsafe
Abi → STRING_LITERAL | RAW_STRING_LITERAL
FunctionParameters →
SelfParam ,?
| ( SelfParam , )? FunctionParam ( , FunctionParam )* ,?
SelfParam → OuterAttribute* ( ShorthandSelf | TypedSelf )
ShorthandSelf → ( & | & Lifetime )? mut? self
FunctionParam → OuterAttribute* ( FunctionParamPattern | … | Type )
FunctionParamPattern → PatternNoTopAlt : ( Type | … )
FunctionReturnType → -> Type
ConstantItem →
const ( IDENTIFIER | _ ) : Type ( = Expression )? ;
StaticItem →
ItemSafety? static mut? IDENTIFIER : Type ( = Expression )? ;
UseDeclaration → use UseTree ;
UseTree →
( SimplePath? :: )? *
| ( SimplePath? :: )? { ( UseTree ( , UseTree )* ,? )? }
| SimplePath ( as ( IDENTIFIER | _ ) )?
AssociatedItem →
OuterAttribute* (
MacroInvocationSemi
| ( Visibility? ( TypeAlias | ConstantItem | Function ) )
)
Module →
unsafe? mod IDENTIFIER ;
| unsafe? mod IDENTIFIER {
InnerAttribute*
Item*
}
Struct →
StructStruct
| TupleStruct
StructStruct →
struct IDENTIFIER GenericParams? WhereClause? ( { StructFields? } | ; )
TupleStruct →
struct IDENTIFIER GenericParams? ( TupleFields? ) WhereClause? ;
StructFields → StructField ( , StructField )* ,?
StructField → OuterAttribute* Visibility? IDENTIFIER : Type
TupleFields → TupleField ( , TupleField )* ,?
TupleField → OuterAttribute* Visibility? Type
Item →
OuterAttribute* ( VisItem | MacroItem )
VisItem →
Visibility?
(
Module
| ExternCrate
| UseDeclaration
| Function
| TypeAlias
| Struct
| Enumeration
| Union
| ConstantItem
| StaticItem
| Trait
| Implementation
| ExternBlock
)
Lexer summary
Lexer
COMMENT →
LINE_COMMENT
| INNER_LINE_DOC
| OUTER_LINE_DOC
| INNER_BLOCK_DOC
| OUTER_BLOCK_DOC
| BLOCK_COMMENT
LINE_COMMENT →
// ( ~[/ ! LF] | // ) ~LF*
| // EOF
| //immediately followed by LF
BLOCK_COMMENT →
/**/
| /***/
| /*
^
( ~[* !] | ** | BLOCK_COMMENT_OR_DOC )
( BLOCK_COMMENT_OR_DOC | ~*/ )*
*/
INNER_LINE_DOC →
//! ^ LINE_DOC_COMMENT_CONTENT ( LF | EOF )
LINE_DOC_COMMENT_CONTENT → ( !CR ~LF )*
INNER_BLOCK_DOC →
/*! ^ ( BLOCK_COMMENT_OR_DOC | BLOCK_CHAR )* */
OUTER_LINE_DOC →
/// ^ LINE_DOC_COMMENT_CONTENT ( LF | EOF )
OUTER_BLOCK_DOC →
/** ![* /]
^
( ~* | BLOCK_COMMENT_OR_DOC )
( BLOCK_COMMENT_OR_DOC | BLOCK_CHAR )*
*/
BLOCK_CHAR → ( !( */ | CR ) CHAR )
BLOCK_COMMENT_OR_DOC →
BLOCK_COMMENT
| OUTER_BLOCK_DOC
| INNER_BLOCK_DOC
WHITESPACE →
U+0009 // Horizontal tab, '\t'
| U+000A // Line feed, '\n'
| U+000B // Vertical tab
| U+000C // Form feed
| U+000D // Carriage return, '\r'
| U+0020 // Space, ' '
| U+0085 // Next line
| U+200E // Left-to-right mark
| U+200F // Right-to-left mark
| U+2028 // Line separator
| U+2029 // Paragraph separator
TAB → U+0009 // Horizontal tab, '\t'
LF → U+000A // Line feed, '\n'
CR → U+000D // Carriage return, '\r'
IDENTIFIER_OR_KEYWORD → ( XID_Start | _ ) XID_Continue*
XID_Start → <XID_Start defined by Unicode>
XID_Continue → <XID_Continue defined by Unicode>
RAW_IDENTIFIER → r# IDENTIFIER_OR_KEYWORD
NON_KEYWORD_IDENTIFIER → IDENTIFIER_OR_KEYWORDexcept a strict or reserved keyword
IDENTIFIER → NON_KEYWORD_IDENTIFIER | RAW_IDENTIFIER
RESERVED_RAW_IDENTIFIER →
r# ( _ | crate | self | Self | super ) !XID_Continue
CHAR → [U+0000-U+D7FF U+E000-U+10FFFF] // a Unicode scalar value
ASCII → [U+0000-U+007F]
NUL → U+0000
EOF → !CHAR // End of file or input
Token →
RESERVED_TOKEN
| RAW_IDENTIFIER
| CHAR_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| C_STRING_LITERAL
| RAW_C_STRING_LITERAL
| FLOAT_LITERAL
| INTEGER_LITERAL
| LIFETIME_TOKEN
| PUNCTUATION
| IDENTIFIER_OR_KEYWORD
SUFFIX → IDENTIFIER_OR_KEYWORDexcept _
SUFFIX_NO_E → ![e E] SUFFIX
CHAR_LITERAL →
‘
( ~[’ \ LF CR TAB] | QUOTE_ESCAPE | ASCII_ESCAPE | UNICODE_ESCAPE )
’ SUFFIX?
QUOTE_ESCAPE → \’ | \“
ASCII_ESCAPE →
\x OCT_DIGIT HEX_DIGIT
| \n | \r | \t | \\ | \0
UNICODE_ESCAPE →
\u{ ( HEX_DIGIT _* )1..=6valid hex char value }
STRING_LITERAL →
“ (
~[” \ CR]
| QUOTE_ESCAPE
| ASCII_ESCAPE
| UNICODE_ESCAPE
| STRING_CONTINUE
)* “ SUFFIX?
STRING_CONTINUE → \ LF
RAW_STRING_LITERAL →
r “ ^ RAW_STRING_CONTENT ” SUFFIX?
| r #n:1..=255 ^ “ RAW_STRING_CONTENT_HASHED ” #n SUFFIX?
RAW_STRING_CONTENT → ( !“ ~CR )*
RAW_STRING_CONTENT_HASHED → ( !( “ #n ) ~CR )*
BYTE_LITERAL →
b’ ^ ( ASCII_FOR_CHAR | BYTE_ESCAPE ) ’ SUFFIX?
ASCII_FOR_CHAR → ![’ \ LF CR TAB] ASCII
BYTE_ESCAPE →
\x HEX_DIGIT HEX_DIGIT
| \n | \r | \t | \\ | \0 | \’ | \“
BYTE_STRING_LITERAL →
b“ ^ ( ASCII_FOR_STRING | BYTE_ESCAPE | STRING_CONTINUE )* “ SUFFIX?
ASCII_FOR_STRING → ![“ \ CR] ASCII
RAW_BYTE_STRING_LITERAL →
br “ ^ RAW_BYTE_STRING_CONTENT ” SUFFIX?
| br #n:1..=255 ^ “ RAW_BYTE_STRING_CONTENT_HASHED ” #n SUFFIX?
RAW_BYTE_STRING_CONTENT → ( !“ ASCII_FOR_RAW )*
RAW_BYTE_STRING_CONTENT_HASHED → ( !( “ #n ) ASCII_FOR_RAW )*
ASCII_FOR_RAW → !CR ASCII
C_STRING_LITERAL →
c“ ^ (
~[“ \ CR NUL]
| BYTE_ESCAPEexcept \0 or \x00
| UNICODE_ESCAPEexcept \u{0}, \u{00}, …, \u{000000}
| STRING_CONTINUE
)* ” SUFFIX?
RAW_C_STRING_LITERAL →
cr “ ^ RAW_C_STRING_CONTENT ” SUFFIX?
| cr #n:1..=255 ^ “ RAW_C_STRING_CONTENT_HASHED ” #n SUFFIX?
RAW_C_STRING_CONTENT → ( !“ ~[CR NUL] )*
RAW_C_STRING_CONTENT_HASHED → ( !( “ #n ) ~[CR NUL] )*
INTEGER_LITERAL →
( BIN_LITERAL | OCT_LITERAL | HEX_LITERAL | DEC_LITERAL ) SUFFIX_NO_E?
DEC_LITERAL → DEC_DIGIT ( DEC_DIGIT | _ )*
BIN_LITERAL → 0b _* BIN_DIGIT ( BIN_DIGIT | _ )*
OCT_LITERAL → 0o _* OCT_DIGIT ( OCT_DIGIT | _ )*
HEX_LITERAL → 0x _* HEX_DIGIT ( HEX_DIGIT | _ )*
BIN_DIGIT → [0-1]
OCT_DIGIT → [0-7]
DEC_DIGIT → [0-9]
HEX_DIGIT → [0-9 a-f A-F]
TUPLE_INDEX → DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL
FLOAT_LITERAL →
DEC_LITERAL ( . DEC_LITERAL )? FLOAT_EXPONENT SUFFIX?
| DEC_LITERAL . DEC_LITERAL SUFFIX_NO_E?
| DEC_LITERAL . !( . | _ | XID_Start )
FLOAT_EXPONENT →
( e | E ) ^ ( + | - )? _* DEC_DIGIT ( DEC_DIGIT | _ )*
RESERVED_NUMBER →
BIN_LITERAL [2-9]
| OCT_LITERAL [8-9]
| ( BIN_LITERAL | OCT_LITERAL | HEX_LITERAL ) . !( . | _ | XID_Start )
| ( BIN_LITERAL | OCT_LITERAL ) ( e | E )
| 0b _* !BIN_DIGIT
| 0o _* !OCT_DIGIT
| 0x _* !HEX_DIGIT
LIFETIME_TOKEN →
RAW_LIFETIME
| ‘ IDENTIFIER_OR_KEYWORD !’
LIFETIME_OR_LABEL →
RAW_LIFETIME
| ‘ NON_KEYWORD_IDENTIFIER !’
RAW_LIFETIME →
‘r# ^ IDENTIFIER_OR_KEYWORD !’
RESERVED_RAW_LIFETIME → ‘r# ( _ | crate | self | Self | super ) !( ’ | XID_Continue )
PUNCTUATION →
…
| ..=
| <<=
| >>=
| !=
| %=
| &&
| &=
| *=
| +=
| -=
| ->
| ..
| /=
| ::
| <-
| <<
| <=
| ==
| =>
| >=
| >>
| ^=
| |=
| ||
| !
| #
| $
| %
| &
| (
| )
| *
| +
| ,
| -
| .
| /
| :
| ;
| <
| =
| >
| ?
| @
| [
| ]
| ^
| {
| |
| }
| ~
RESERVED_TOKEN →
RESERVED_GUARDED_STRING_LITERAL
| RESERVED_NUMBER
| RESERVED_POUNDS
| RESERVED_RAW_IDENTIFIER
| RESERVED_RAW_LIFETIME
| RESERVED_TOKEN_DOUBLE_QUOTE
| RESERVED_TOKEN_LIFETIME
| RESERVED_TOKEN_POUND
| RESERVED_TOKEN_SINGLE_QUOTE
RESERVED_TOKEN_DOUBLE_QUOTE →
IDENTIFIER_OR_KEYWORDexcept b or c or r or br or cr “
RESERVED_TOKEN_SINGLE_QUOTE →
IDENTIFIER_OR_KEYWORDexcept b ’
RESERVED_TOKEN_POUND →
IDENTIFIER_OR_KEYWORDexcept r or br or cr #
RESERVED_TOKEN_LIFETIME →
’ IDENTIFIER_OR_KEYWORDexcept r #
RESERVED_GUARDED_STRING_LITERAL → #+ STRING_LITERAL
RESERVED_POUNDS → #2..
Patterns summary
Syntax
Pattern → |? PatternNoTopAlt ( | PatternNoTopAlt )*
PatternNoTopAlt →
PatternWithoutRange
| RangePattern
PatternWithoutRange →
LiteralPattern
| IdentifierPattern
| WildcardPattern
| RestPattern
| ReferencePattern
| StructPattern
| TupleStructPattern
| TuplePattern
| GroupedPattern
| SlicePattern
| PathPattern
| MacroInvocation
LiteralPattern → -? LiteralExpression
IdentifierPattern → ref? mut? IDENTIFIER ( @ PatternNoTopAlt )?
WildcardPattern → _
RestPattern → ..
RangePattern →
RangeExclusivePattern
| RangeInclusivePattern
| RangeFromPattern
| RangeToExclusivePattern
| RangeToInclusivePattern
| ObsoleteRangePattern
RangeExclusivePattern →
RangePatternBound .. RangePatternBound
RangeInclusivePattern →
RangePatternBound ..= RangePatternBound
RangeFromPattern →
RangePatternBound ..
RangeToExclusivePattern →
.. RangePatternBound
RangeToInclusivePattern →
..= RangePatternBound
ObsoleteRangePattern →
RangePatternBound … RangePatternBound
RangePatternBound →
LiteralPattern
| PathExpression
ReferencePattern → ( & | && ) mut? PatternWithoutRange
StructPattern →
PathInExpression {
StructPatternElements?
}
StructPatternElements →
StructPatternFields ( , | , StructPatternEtCetera )?
| StructPatternEtCetera
StructPatternFields →
StructPatternField ( , StructPatternField )*
StructPatternField →
OuterAttribute*
(
TUPLE_INDEX : Pattern
| IDENTIFIER : Pattern
| ref? mut? IDENTIFIER
)
TupleStructPattern → PathInExpression ( TupleStructItems? )
TupleStructItems → Pattern ( , Pattern )* ,?
TuplePattern → ( TuplePatternItems? )
TuplePatternItems →
Pattern ,
| RestPattern
| Pattern ( , Pattern )+ ,?
GroupedPattern → ( Pattern )
SlicePattern → [ SlicePatternItems? ]
SlicePatternItems → Pattern ( , Pattern )* ,?
Expressions summary
Syntax
Expression →
ExpressionWithoutBlock
| ExpressionWithBlock
ExpressionWithoutBlock →
OuterAttribute* ExpressionWithoutBlockNoAttrs
ExpressionWithoutBlockNoAttrs →
LiteralExpression
| PathExpression
| OperatorExpression
| GroupedExpression
| ArrayExpression
| AwaitExpression
| IndexExpression
| TupleExpression
| TupleIndexingExpression
| StructExpression
| CallExpression
| MethodCallExpression
| FieldExpression
| ClosureExpression
| AsyncBlockExpression
| ContinueExpression
| BreakExpression
| RangeExpression
| ReturnExpression
| UnderscoreExpression
| MacroInvocation
ExpressionWithBlock →
OuterAttribute* ExpressionWithBlockNoAttrs
ExpressionWithBlockNoAttrs →
BlockExpression
| ConstBlockExpression
| UnsafeBlockExpression
| LoopExpression
| IfExpression
| MatchExpression
IfExpression →
if Conditions BlockExpression
( else ( BlockExpression | IfExpression ) )?
Conditions →
Expressionexcept StructExpression
| LetChain
LetChain → LetChainCondition ( && LetChainCondition )*
LetChainCondition →
Expressionexcept ExcludedConditions
| OuterAttribute* let Pattern = Scrutineeexcept ExcludedConditions
ExcludedConditions →
StructExpression
| LazyBooleanExpression
| RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| AssignmentExpression
| CompoundAssignmentExpression
GroupedExpression → ( Expression )
CallExpression → Expression ( CallParams? )
CallParams → Expression ( , Expression )* ,?
AwaitExpression → Expression . await
LoopExpression →
LoopLabel? (
InfiniteLoopExpression
| PredicateLoopExpression
| IteratorLoopExpression
| LabelBlockExpression
)
InfiniteLoopExpression → loop BlockExpression
PredicateLoopExpression → while Conditions BlockExpression
IteratorLoopExpression →
for Pattern in Expressionexcept StructExpression BlockExpression
LoopLabel → LIFETIME_OR_LABEL :
BreakExpression → break LIFETIME_OR_LABEL? Expression?
LabelBlockExpression → BlockExpression
ContinueExpression → continue LIFETIME_OR_LABEL?
RangeExpression →
RangeExpr
| RangeFromExpr
| RangeToExpr
| RangeFullExpr
| RangeInclusiveExpr
| RangeToInclusiveExpr
RangeExpr → Expression .. Expression
RangeFromExpr → Expression ..
RangeToExpr → .. Expression
RangeFullExpr → ..
RangeInclusiveExpr → Expression ..= Expression
RangeToInclusiveExpr → ..= Expression
BlockExpression →
{
InnerAttribute*
Statements?
}
Statements →
Statement+
| Statement+ ExpressionWithoutBlock
| ExpressionWithoutBlock
AsyncBlockExpression → async move? BlockExpression
ConstBlockExpression → const BlockExpression
UnsafeBlockExpression → unsafe BlockExpression
OperatorExpression →
BorrowExpression
| DereferenceExpression
| TryPropagationExpression
| NegationExpression
| ArithmeticOrLogicalExpression
| ComparisonExpression
| LazyBooleanExpression
| TypeCastExpression
| AssignmentExpression
| CompoundAssignmentExpression
BorrowExpression →
( & | && ) Expression
| ( & | && ) mut Expression
| ( & | && ) raw const Expression
| ( & | && ) raw mut Expression
DereferenceExpression → * Expression
TryPropagationExpression → Expression ?
NegationExpression →
- Expression
| ! Expression
ArithmeticOrLogicalExpression →
Expression + Expression
| Expression - Expression
| Expression * Expression
| Expression / Expression
| Expression % Expression
| Expression & Expression
| Expression | Expression
| Expression ^ Expression
| Expression << Expression
| Expression >> Expression
ComparisonExpression →
Expression == Expression
| Expression != Expression
| Expression > Expression
| Expression < Expression
| Expression >= Expression
| Expression <= Expression
LazyBooleanExpression →
Expression || Expression
| Expression && Expression
TypeCastExpression → Expression as TypeNoBounds
AssignmentExpression → Expression = Expression
CompoundAssignmentExpression →
Expression += Expression
| Expression -= Expression
| Expression *= Expression
| Expression /= Expression
| Expression %= Expression
| Expression &= Expression
| Expression |= Expression
| Expression ^= Expression
| Expression <<= Expression
| Expression >>= Expression
MatchExpression →
match Scrutinee {
InnerAttribute*
MatchArms?
}
Scrutinee → Expressionexcept StructExpression
MatchArms →
( MatchArm => ( ExpressionWithoutBlock , | ExpressionWithBlock ,? ) )*
MatchArm => Expression ,?
MatchArm → OuterAttribute* Pattern MatchArmGuard?
MatchArmGuard → if MatchConditions
MatchConditions →
MatchGuardChain
| Expression
MatchGuardChain → MatchGuardCondition ( && MatchGuardCondition )*
MatchGuardCondition →
Expressionexcept ExcludedMatchConditions
| OuterAttribute* let Pattern = MatchGuardScrutinee
MatchGuardScrutinee → Expressionexcept ExcludedMatchConditions
ExcludedMatchConditions →
LazyBooleanExpression
| RangeExpr
| RangeFromExpr
| RangeInclusiveExpr
| AssignmentExpression
| CompoundAssignmentExpression
MethodCallExpression → Expression . PathExprSegment ( CallParams? )
ReturnExpression → return Expression?
StructExpression →
PathInExpression { ( StructExprFields | StructBase )? }
StructExprFields →
StructExprField ( , StructExprField )* ( , StructBase | ,? )
StructExprField →
OuterAttribute*
(
IDENTIFIER
| ( IDENTIFIER | TUPLE_INDEX ) : Expression
)
StructBase → .. Expression
ClosureExpression →
async?
move?
( || | | ClosureParameters? | )
( Expression | -> TypeNoBounds BlockExpression )
ClosureParameters → ClosureParam ( , ClosureParam )* ,?
ClosureParam → OuterAttribute* PatternNoTopAlt ( : Type )?
PathExpression →
PathInExpression
| QualifiedPathInExpression
LiteralExpression →
CHAR_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| C_STRING_LITERAL
| RAW_C_STRING_LITERAL
| INTEGER_LITERAL
| FLOAT_LITERAL
| true
| false
ArrayExpression → [ ArrayElements? ]
ArrayElements →
Expression ( , Expression )* ,?
| Expression ; Expression
IndexExpression → Expression [ Expression ]
TupleExpression → ( TupleElements? )
TupleElements → ( Expression , )+ Expression?
Types summary
Syntax
Type →
TypeNoBounds
| ImplTraitType
| TraitObjectType
TypeNoBounds →
ParenthesizedType
| ImplTraitTypeOneBound
| TraitObjectTypeOneBound
| TypePath
| TupleType
| NeverType
| RawPointerType
| ReferenceType
| ArrayType
| SliceType
| InferredType
| QualifiedPathInType
| BareFunctionType
| MacroInvocation
ParenthesizedType → ( Type )
TupleType →
( )
| ( ( Type , )+ Type? )
BareFunctionType →
ForLifetimes? FunctionTypeQualifiers fn
( FunctionParametersMaybeNamedVariadic? ) BareFunctionReturnType?
FunctionTypeQualifiers → unsafe? ( extern Abi? )?
BareFunctionReturnType → -> TypeNoBounds
FunctionParametersMaybeNamedVariadic →
MaybeNamedFunctionParameters | MaybeNamedFunctionParametersVariadic
MaybeNamedFunctionParameters →
MaybeNamedParam ( , MaybeNamedParam )* ,?
MaybeNamedParam →
OuterAttribute* ( ( IDENTIFIER | _ ) : )? Type
MaybeNamedFunctionParametersVariadic →
( MaybeNamedParam , )* MaybeNamedParam , OuterAttribute* …
InferredType → _
TraitObjectType → dyn? TypeParamBounds
TraitObjectTypeOneBound → dyn? TraitBound
ImplTraitType → impl TypeParamBounds
ImplTraitTypeOneBound → impl TraitBound
ArrayType → [ Type ; Expression ]
ReferenceType → & Lifetime? mut? TypeNoBounds
RawPointerType → * ( mut | const ) TypeNoBounds
NeverType → !
Paths summary
Syntax
SimplePath →
::? SimplePathSegment ( :: SimplePathSegment )*
SimplePathSegment →
IDENTIFIER | super | self | crate | $crate
PathInExpression →
::? PathExprSegment ( :: PathExprSegment )*
PathExprSegment →
PathIdentSegment ( :: GenericArgs )?
PathIdentSegment →
IDENTIFIER | super | self | Self | crate | $crate
GenericArgs →
< >
| < ( GenericArg , )* GenericArg ,? >
GenericArg →
Lifetime | Type | GenericArgsConst | GenericArgsBinding | GenericArgsBounds
GenericArgsConst →
BlockExpression
| LiteralExpression
| - LiteralExpression
| SimplePathSegment
GenericArgsBinding →
IDENTIFIER GenericArgs? = Type
GenericArgsBounds →
IDENTIFIER GenericArgs? : TypeParamBounds
QualifiedPathInExpression → QualifiedPathType ( :: PathExprSegment )+
QualifiedPathType → < Type ( as TypePath )? >
QualifiedPathInType → QualifiedPathType ( :: TypePathSegment )+
TypePath → ::? TypePathSegment ( :: TypePathSegment )*
TypePathSegment → PathIdentSegment ( ::? ( GenericArgs | TypePathFn ) )?
TypePathFn → ( TypePathFnInputs? ) ( -> TypeNoBounds )?
TypePathFnInputs → Type ( , Type )* ,?
Statements summary
Syntax
Statement →
;
| Item
| LetStatement
| ExpressionStatement
| OuterAttribute* MacroInvocationSemi
LetStatement →
OuterAttribute* let PatternNoTopAlt ( : Type )?
(
= Expression
| = Expressionexcept LazyBooleanExpression or end with a } else BlockExpression
)? ;
ExpressionStatement →
ExpressionWithoutBlock ;
| ExpressionWithBlock ;?
Configuration summary
Syntax
ConfigurationPredicate →
ConfigurationOption
| ConfigurationAll
| ConfigurationAny
| ConfigurationNot
| true
| false
ConfigurationOption →
IDENTIFIER ( = ( STRING_LITERAL | RAW_STRING_LITERAL ) )?
ConfigurationAll →
all ( ConfigurationPredicateList? )
ConfigurationAny →
any ( ConfigurationPredicateList? )
ConfigurationNot →
not ( ConfigurationPredicate )
ConfigurationPredicateList →
ConfigurationPredicate ( , ConfigurationPredicate )* ,?
CfgAttribute → cfg ( ConfigurationPredicate )
CfgAttrAttribute → cfg_attr ( ConfigurationPredicate , CfgAttrs? )
CfgAttrs → Attr ( , Attr )* ,?
CfgSelectArms →
CfgSelectConfigurationPredicate =>
(
{ ^ TokenTree } ,? CfgSelectArms?
| ExpressionWithBlockNoAttrs ,? CfgSelectArms?
| ExpressionWithoutBlockNoAttrs ( , CfgSelectArms? )?
)
CfgSelectConfigurationPredicate →
ConfigurationPredicate | _
Syntax index
This appendix provides an index of tokens and common forms with links to where those elements are defined.
키워드
Operators and punctuation
주석
| Comment | Use |
|---|---|
// | line comment |
//! | inner line comment |
/// | outer line doc comment |
/*…*/ | block comment |
/*!…*/ | inner block doc comment |
/**…*/ | outer block doc comment |
Other tokens
| Token | Use |
|---|---|
ident | identifiers |
r#ident | raw identifiers |
'ident | lifetimes and loop labels |
'r#ident | raw lifetimes and loop labels |
…u8, …i32, …f64, …usize, … | number literals |
"…" | string literals |
r"…", r#"…"#, r##"…"##, … | raw string literals |
b"…" | byte string literals |
br"…", br#"…"#, br##"…"##, … | raw byte string literals |
'…' | character literals |
b'…' | byte literals |
c"…" | C string literals |
cr"…", cr#"…"#, cr##"…"##, … | raw C string literals |
매크로
| 구문 | Use |
|---|---|
ident!(…)ident! {…}ident![…] | macro invocations |
$ident | macro metavariable |
$ident:kind | macro matcher fragment specifier |
$(…)… | macro repetition |
속성
| 구문 | Use |
|---|---|
#[meta] | outer attribute |
#![meta] | inner attribute |
표현식
아이템
Items are the components of a crate.
| Item | Use |
|---|---|
mod ident;mod ident {…} | modules |
use path; | use declarations |
fn ident(…) {…} | functions |
type Type = Type; | type aliases |
struct ident {…} | structs |
enum ident {…} | enumerations |
union ident {…} | unions |
trait ident {…} | traits |
impl Type {…}impl Type for Trait {…} | implementations |
const ident = expr; | constant items |
static ident = expr; | static items |
extern "C" {…} | external blocks |
fn ident<…>(…) …struct ident<…> {…}enum ident<…> {…}impl<…> Type<…> {…} | generic definitions |
타입 표현식
Type expressions are used to refer to types.
| 유형 | Use |
|---|---|
bool, u8, f64, str, … | primitive types |
for<…> | higher-ranked trait bounds |
T: TraitA + TraitB | trait bounds |
T: 'a + 'b | lifetime bounds |
T: TraitA + 'a | trait and lifetime bounds |
T: ?Sized | relaxed trait bounds |
[Type; len] | array types |
(Type, …) | tuple types |
[Type] | slice types |
(Type) | parenthesized types |
impl Trait | impl trait types, anonymous type parameters |
dyn Trait | trait object types |
identident::… | type paths (can refer to structs, enumerations, unions, type aliases, traits, generics, etc.) |
Type<…>Trait<…> | generic arguments (e.g. Vec<u8>) |
Trait<ident = Type> | associated type bindings (e.g. Iterator<Item = T>) |
Trait<ident: …> | associated type bounds (e.g. Iterator<Item: Send>) |
&Type&mut Type | reference types |
*mut Type*const Type | raw pointer types |
fn(…) -> Type | function pointer types |
_ | inferred type, inferred const |
'_ | placeholder lifetime |
! | never type |
패턴
Patterns are used to match values.
| Pattern | Use |
|---|---|
"foo", 'a', 123, 2.4, … | literal patterns |
ident | identifier patterns |
_ | wildcard pattern |
.. | rest pattern |
a.., ..b, a..b, a..=b, ..=b | range patterns |
&pattern&mut pattern | reference patterns |
path {…} | struct patterns |
path(…) | tuple struct patterns |
(pattern, …) | tuple patterns |
(pattern) | grouped patterns |
[pattern, …] | slice patterns |
CONST, Enum::Variant, … | path patterns |
Appendix: Macro follow-set ambiguity formal specification
이 페이지는 예제를 통한 매크로 를 위한 follow 규칙의 공식 명세를 문서화합니다. 이 규칙들은 원래 RFC 550에서 명시되었으며, 이 텍스트의 대부분은 해당 RFC에서 복사되고 이후의 RFC들에서 확장된 것입니다.
Definitions & conventions
macro: 소스 코드에서foo!(...)와 같이 호출 가능한 모든 것.MBE: 예제를 통한 매크로(macro-by-example),macro_rules에 의해 정의된 매크로.matcher:macro_rules호출 내 규칙의 왼쪽 부분(LHS), 또는 그 일부분.macro parser: 모든 매처(matchers)로부터 파생된 문법을 사용하여 입력을 파싱하는 러스트 파서 내의 코드 조각.fragment: 주어진 매처가 수용할(또는 “일치시킬”) 러스트 구문 클래스.repetition: 규칙적인 반복 패턴을 따르는 프래그먼트.NT: 비단말(non-terminal), 매처에 나타날 수 있는 다양한 “메타 변수” 또는 반복 매처. MBE 구문에서 시작 부분의$문자로 지정됩니다.simple NT: “메타 변수” 비단말 (아래에서 더 자세히 논의됨).complex NT: 반복 연산자(*,+,?)를 통해 지정된, 반복 일치 비단말.token: 매처의 원자적 요소. 즉, 식별자, 연산자, 여는/닫는 구분자, 그리고 단순 NT(simple NT).token tree: 토큰(리프), 복합 NT(complex NT), 그리고 토큰 트리의 유한 시퀀스로 형성된 트리 구조.delimiter token: 한 프래그먼트의 끝과 다음 프래그먼트의 시작을 나누기 위한 토큰.separator token: 복합 NT에서 일치된 반복의 각 요소 쌍을 구분하는 선택적인 구분자 토큰.separated complex NT: 자체 구분자 토큰을 가진 복합 NT.delimited sequence: 시퀀스의 시작과 끝에 적절한 여는 구분자와 닫는 구분자가 있는 토큰 트리 시퀀스.empty fragment: 토큰을 구분하는 보이지 않는 러스트 구문 클래스. 즉, 공백(whitespace) 또는 (일부 어휘 문맥에서) 빈 토큰 시퀀스.fragment specifier: 단순 NT(simple NT)에서 해당 NT가 수용하는 프래그먼트를 지정하는 식별자.language: 문맥 자유 언어(context-free language).
예:
#![allow(unused)]
fn main() {
macro_rules! i_am_an_mbe {
(start $foo:expr $($i:ident),* end) => ($foo)
}
}
(start $foo:expr $($i:ident),* end) 는 매처(matcher)입니다. 전체 매처는 구분된 시퀀스(여는 구분자 ( 와 닫는 구분자 ) 가 있는)이며, $foo 와 $i 는 각각 expr 과 ident 를 프래그먼트 지정자로 가진 단순 NT입니다.
$(i:ident),* 또한 하나의 NT입니다. 이는 쉼표로 구분된 식별자들의 반복과 일치하는 복합 NT(complex NT)입니다. , 는 이 복합 NT의 구분자 토큰(separator token)입니다. 이는 일치된 프래그먼트의 각 요소 쌍(있는 경우) 사이에 나타납니다.
복합 NT의 또 다른 예는 $(hi $e:expr ;)+ 입니다. 이는 hi <expr>; hi <expr>; ... 와 같은 형태의 프래그먼트 중 hi <expr>; 가 최소 한 번 이상 나타나는 것과 일치합니다. 이 복합 NT는 전용 구분자 토큰을 가지고 있지 않음에 유의하십시오.
(러스트의 파서는 구분된 시퀀스가 항상 토큰 트리 구조의 적절한 중첩과 여는/닫는 구분자의 올바른 일치와 함께 발생하도록 보장함에 유의하십시오.)
우리는 매처를 나타내기 위해 변수 “M“을, 임의의 개별 토큰을 위해 변수 “t“와 “u“를, 임의의 토큰 트리를 위해 변수 “tt“와 “uu“를 사용하는 경향이 있습니다. (“tt“의 사용은 프래그먼트 지정자로서의 추가적인 역할 때문에 잠재적인 모호성을 제시하지만, 문맥상 어떤 해석을 의미하는지 명확할 것입니다.)
“SEP“는 구분자 토큰을, “OP“는 반복 연산자 *, +, ? 를, “OPEN”/“CLOSE“는 구분된 시퀀스를 둘러싼 일치하는 토큰 쌍(예: [ 및 ])을 나타냅니다.
그리스 문자 “α”, “β”, “γ”, “δ“는 잠재적으로 비어 있을 수 있는 토큰 트리 시퀀스를 나타냅니다. (단, 그리스 문자 “ε”(입실론)은 이 설명에서 특별한 역할을 수행하며 토큰 트리 시퀀스를 나타내지 않습니다.)
- 이 그리스 문자 관례는 대개 시퀀스의 존재가 기술적인 세부 사항일 때만 채택됩니다. 특히, 우리가 토큰 트리 시퀀스에 대해 작업하고 있음을 강조 하고자 할 때는 그리스 문자가 아닌 “tt …“라는 표기법을 사용합니다.
매처는 단지 하나의 토큰 트리라는 점에 유의하십시오. 위에서 언급했듯이 “단순 NT“는 메타 변수 NT이며, 따라서 반복이 아닙니다. 예를 들어 $foo:ty 는 단순 NT이지만 $($foo:ty)+ 는 복합 NT입니다.
또한 이 형식화된 체계(formalism)의 문맥에서 “토큰“이라는 용어는 일반적으로 단순 NT를 포함 함에 유의하십시오.
마지막으로, 독자 여러분은 이 형식화된 체계의 정의에 따라 어떤 단순 NT도 빈 프래그먼트와 일치하지 않으며, 마찬가지로 어떤 토큰도 러스트 구문의 빈 프래그먼트와 일치하지 않는다는 점을 기억하는 것이 유용합니다. (따라서 빈 프래그먼트와 일치할 수 있는 유일한 NT는 복합 NT뿐입니다.) 이는 실제로 사실이 아닌데, 왜냐하면 vis 매처는 빈 프래그먼트와 일치할 수 있기 때문입니다. 따라서 이 체계의 목적을 위해 우리는 $v:vis 를 실제로는 $($v:vis)? 인 것처럼 취급할 것이며, 매처가 빈 프래그먼트와 일치해야 한다는 요구 사항을 둘 것입니다.
The matcher invariants
유효한 매처가 되려면 다음 세 가지 불변성을 충족해야 합니다. FIRST 및 FOLLOW의 정의는 나중에 설명됩니다.
- 매처
M내의 임의의 두 연속된 토큰 트리 시퀀스(M = ... tt uu ...)에 대해uu ...가 비어 있지 않다면, FOLLOW(... tt) ∪ {ε} ⊇ FIRST(uu ...)를 만족해야 합니다. - 매처 내의 임의의 구분된 복합 NT
M = ... $(tt ...) SEP OP ...에 대해,SEP∈ FOLLOW(tt ...)여야 합니다. - 매처 내의 구분되지 않은 복합 NT
M = ... $(tt ...) OP ...에 대해, OP =*또는+인 경우, FOLLOW(tt ...) ⊇ FIRST(tt ...)여야 합니다.
The first invariant says that whatever actual token that comes after a matcher, if any, must be somewhere in the predetermined follow set. This ensures that a legal macro definition will continue to assign the same determination as to where ... tt ends and uu ... begins, even as new syntactic forms are added to the language.
두 번째 불변성은 구분된 복합 NT가 해당 NT 내부 콘텐츠에 대해 미리 결정된 follow 집합의 일부인 구분자 토큰을 사용해야 함을 의미합니다. 이는 새로운 구문 형태가 언어에 추가되더라도, 적법한 매크로 정의가 입력 프래그먼트를 동일한 tt ... 들의 구분된 시퀀스로 계속 파싱하도록 보장합니다.
세 번째 불변성은 사이에 구분이 없이 동일한 것의 두 개 이상의 사본과 일치할 수 있는 복합 NT가 있을 때, 첫 번째 불변성에 따라 이들이 서로 옆에 놓이는 것이 허용되어야 함을 의미합니다. 이 불변성은 또한 이들이 비어 있지 않을 것을 요구하며, 이는 발생 가능한 모호성을 제거합니다.
참고: 세 번째 불변성은 과거의 실수와 해당 동작에 대한 상당한 의존성 때문에 현재 강제되지 않고 있습니다. 앞으로 이에 대해 어떻게 할지는 현재 결정되지 않았습니다. 이 동작을 준수하지 않는 매크로는 미래의 러스트 에디션에서 유효하지 않게 될 수 있습니다. 트래킹 이슈 를 참조하십시오.
비공식적인 FIRST 및 FOLLOW
주어진 매처 M은 세 가지 집합 FIRST(M), LAST(M), FOLLOW(M)에 매핑됩니다.
이 세 가지 집합은 각각 토큰들로 구성됩니다. FIRST(M)과 LAST(M)은 또한 M이 빈 프래그먼트와 일치할 수 있음을 나타내는 구별된 비토큰 요소 ε(“입실론”)을 포함할 수 있습니다. (단, FOLLOW(M)은 항상 토큰들의 집합입니다.)
비공식적으로는 다음과 같습니다:
- FIRST(M): 프래그먼트를 M과 일치시킬 때 잠재적으로 가장 먼저 사용되는 토큰들을 수집합니다.
- LAST(M): 프래그먼트를 M과 일치시킬 때 잠재적으로 가장 마지막에 사용되는 토큰들을 수집합니다.
-
FOLLOW(M): M과 일치하는 어떤 프래그먼트 직후에 올 수 있도록 허용된 토큰들의 집합입니다.
다시 말해, t ∈ FOLLOW(M)일 필요충분조건은 다음을 만족하는 (잠재적으로 비어 있을 수 있는) 토큰 시퀀스 α, β, γ, δ가 존재하는 것입니다.
-
M이 β와 일치하고,
-
t가 γ와 일치하며,
-
α β γ δ를 연결한 결과가 파싱 가능한 러스트 프로그램인 경우.
-
우리는 모든 토큰(단순 NT 포함)의 집합을 나타내기 위해 ANYTOKEN이라는 약어를 사용합니다. 예를 들어, 매처 M 뒤에 어떤 토큰이 와도 적법하다면, FOLLOW(M) = ANYTOKEN입니다.
(위의 비공식적인 설명에 대한 이해를 점검하기 위해, 독자 여러분은 공식 정의를 읽기 전에 FIRST/LAST의 예시 로 건너뛰어 확인해 볼 수 있습니다.)
FIRST, LAST
다음은 FIRST와 LAST에 대한 공식적인 귀납적 정의입니다.
“A ∪ B“는 합집합을, “A ∩ B“는 교집합을, “A \ B“는 차집합(즉, A에는 존재하지만 B에는 존재하지 않는 모든 요소)을 나타냅니다.
FIRST
FIRST(M)은 시퀀스 M과 그 첫 번째 토큰 트리(있는 경우)의 구조에 따른 케이스 분석을 통해 정의됩니다.
- M이 빈 시퀀스라면, FIRST(M) = { ε }입니다.
-
M이 토큰 t로 시작한다면, FIRST(M) = { t }입니다.
(참고: 이는 M이 구분된 토큰 트리 시퀀스로 시작하는 경우
M = OPEN tt ... CLOSE ...를 포함하며, 이 경우t = OPEN이므로 FIRST(M) = {OPEN}이 됩니다.)(참고: 이는 어떤 단순 NT도 빈 프래그먼트와 일치하지 않는다는 속성에 결정적으로 의존합니다.)
-
그 외의 경우, M은 복합 NT로 시작하는 토큰 트리 시퀀스입니다:
M = $( tt ... ) OP α, 또는M = $( tt ... ) SEP OP α(여기서α는 매처의 나머지 부분인, 잠재적으로 비어 있을 수 있는 토큰 트리 시퀀스입니다).- SEP가 존재하고 ε ∈ FIRST(
tt ...)라면 SEP_SET(M) = { SEP }이고, 그렇지 않으면 SEP_SET(M) = {}라고 합시다.
- SEP가 존재하고 ε ∈ FIRST(
-
OP가
*또는?라면 ALPHA_SET(M) = FIRST(α)이고, OP가+라면 ALPHA_SET(M) = {}라고 합시다. -
FIRST(M) = (FIRST(
tt ...) \ {ε}) ∪ SEP_SET(M) ∪ ALPHA_SET(M)입니다.
복합 NT에 대한 정의는 정당화될 필요가 있습니다. SEP_SET(M)은 구분자가 정의되어 있고 반복되는 프래그먼트가 비어 있을 수 있는 경우, 구분자가 M의 유효한 첫 번째 토큰이 될 수 있는 가능성을 정의합니다. ALPHA_SET(M)은 복합 NT가 비어 있을 수 있는 가능성을 정의하며, 이는 M의 유효한 첫 번째 토큰들이 뒤따르는 토큰 트리 시퀀스 α 의 토큰들임을 의미합니다. 이는 * 또는 ? 가 사용되어 반복 횟수가 0일 수 있는 경우에 발생합니다. 이론적으로는 잠재적으로 비어 있을 수 있는 반복 프래그먼트와 함께 + 가 사용된 경우에도 발생할 수 있으나, 이는 세 번째 불변성에 의해 금지됩니다.
그 지점으로부터, FIRST(M)은 SEP_SET(M) 또는 ALPHA_SET(M)의 모든 토큰을 포함할 수 있으며, 복합 NT 일치가 비어 있지 않다면 FIRST(tt ...)로 시작하는 모든 토큰 또한 가능합니다. 마지막으로 고려할 부분은 ε입니다. SEP_SET(M)과 FIRST(tt ...) \ {ε}은 ε을 포함할 수 없지만, ALPHA_SET(M)은 포함할 수 있습니다. 따라서 이 정의는 ε ∈ ALPHA_SET(M)인 경우에만 M이 ε을 수용하도록 허용합니다. 이는 복합 NT 사례에서 M이 ε을 수용하려면 복합 NT와 α가 모두 이를 수용해야 하기 때문에 정확합니다. 만약 OP = + 라면, 복합 NT가 비어 있을 수 없음을 의미하므로 정의에 따라 ε ∉ ALPHA_SET(M)입니다. 그 외의 경우 복합 NT는 0번의 반복을 수용할 수 있으며, 이때 ALPHA_SET(M) = FOLLOW(α)가 됩니다. 따라서 이 정의는 ε에 대해서도 정확합니다.
LAST
LAST(M)은 M 자체(토큰 트리 시퀀스)에 대한 케이스 분석을 통해 정의됩니다.
- M이 빈 시퀀스라면, LAST(M) = { ε }입니다.
- M이 단일 토큰 t라면, LAST(M) = { t }입니다.
-
M이 0번 이상 반복되는 단일 복합 NT인 경우,
M = $( tt ... ) *또는M = $( tt ... ) SEP *입니다.-
SEP가 존재한다면 sep_set = { SEP }이고, 그렇지 않으면 sep_set = {}라고 합시다.
-
ε ∈ LAST(
tt ...)라면 LAST(M) = LAST(tt ...) ∪ sep_set입니다. -
그 외의 경우, 시퀀스
tt ...는 반드시 비어 있지 않아야 합니다. LAST(M) = LAST(tt ...) ∪ {ε}입니다.
-
-
M이 1번 이상 반복되는 단일 복합 NT인 경우,
M = $( tt ... ) +또는M = $( tt ... ) SEP +입니다.-
SEP가 존재한다면 sep_set = { SEP }이고, 그렇지 않으면 sep_set = {}라고 합시다.
-
ε ∈ LAST(
tt ...)라면 LAST(M) = LAST(tt ...) ∪ sep_set입니다. -
그 외의 경우, 시퀀스
tt ...는 반드시 비어 있지 않아야 합니다. LAST(M) = LAST(tt ...)입니다.
-
- M이 0번 또는 1번 반복되는 단일 복합 NT인 경우(
M = $( tt ...) ?), LAST(M) = LAST(tt ...) ∪ {ε}입니다.
- M이 구분된 토큰 트리 시퀀스
OPEN tt ... CLOSE인 경우, LAST(M) = {CLOSE}입니다.
-
M이 비어 있지 않은 토큰 트리 시퀀스
tt uu ...인 경우,-
ε ∈ LAST(
uu ...)라면, LAST(M) = LAST(tt) ∪ (LAST(uu ...) \ { ε })입니다. -
그 외의 경우, 시퀀스
uu ...는 반드시 비어 있지 않아야 하며, LAST(M) = LAST(uu ...)입니다.
-
FIRST 및 LAST의 예시
다음은 FIRST와 LAST의 몇 가지 예시입니다. (특히 입력 조각들 사이의 상호 작용에 따라 특별한 ε 요소가 어떻게 도입되고 제거되는지 유의하십시오.)
첫 번째 예시는 매처 분석이 어떻게 구성되는지 상세히 설명하기 위해 트리 구조로 제시됩니다. (일부 단순한 서브트리들은 생략되었습니다.)
INPUT: $( $d:ident $e:expr );* $( $( h )* );* $( f ; )+ g
~~~~~~~~ ~~~~~~~ ~
| | |
FIRST: { $d:ident } { $e:expr } { h }
INPUT: $( $d:ident $e:expr );* $( $( h )* );* $( f ; )+
~~~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~
| | |
FIRST: { $d:ident } { h, ε } { f }
INPUT: $( $d:ident $e:expr );* $( $( h )* );* $( f ; )+ g
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~ ~~~~~~~~~ ~
| | | |
FIRST: { $d:ident, ε } { h, ε, ; } { f } { g }
INPUT: $( $d:ident $e:expr );* $( $( h )* );* $( f ; )+ g
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
FIRST: { $d:ident, h, ;, f }
따라서:
- FIRST(
$($d:ident $e:expr );* $( $(h)* );* $( f ;)+ g) = {$d:ident,h,;,f}
하지만 다음 사항에 유의하십시오:
- FIRST(
$($d:ident $e:expr );* $( $(h)* );* $($( f ;)+ g)*) = {$d:ident,h,;,f, ε }
다음은 LAST에 대한 유사한 예시들입니다.
- LAST(
$d:ident $e:expr) = {$e:expr} - LAST(
$( $d:ident $e:expr );*) = {$e:expr, ε } - LAST(
$( $d:ident $e:expr );* $(h)*) = {$e:expr, ε,h} - LAST(
$( $d:ident $e:expr );* $(h)* $( f ;)+) = {;} - LAST(
$( $d:ident $e:expr );* $(h)* $( f ;)+ g) = {g}
FOLLOW(M)
마지막으로, FOLLOW(M)의 정의는 다음과 같이 구성됩니다. pat, expr 등은 주어진 프래그먼트 지정자를 가진 단순 비단말(nonterminals)을 나타냅니다.
- FOLLOW(pat) = {
=>,,,=,|,if,in}입니다.
- FOLLOW(expr) = FOLLOW(expr_2021) = FOLLOW(stmt) = {
=>,,,;}입니다.
- FOLLOW(ty) = FOLLOW(path) = {
{,[,,,=>,:,=,>,>>,;,|,as,where, block 비단말}입니다.
- FOLLOW(vis) = {
,l 원시(raw)가 아닌priv를 제외한 모든 키워드 또는 식별자; 타입을 시작할 수 있는 모든 토큰; ident, ty, 그리고 path 비단말}입니다.
- FOLLOW(t) = ANYTOKEN (block, ident, tt, item, lifetime, literal, meta 단순 비단말 및 모든 단말(terminals)을 포함한 다른 모든 단순 토큰의 경우).
- 다른 모든 M에 대해 FOLLOW(M)은, t가 (LAST(M) \ {ε})의 범위에 있을 때 FOLLOW(t)의 교집합으로 정의됩니다.
타입을 시작할 수 있는 토큰은 이 글을 쓰는 시점을 기준으로 {(, [, !, *, &, &&, ?, 라이프타임, >, >>, ::, 키워드가 아닌 모든 식별자, super, self, Self, extern, crate, $crate, _, for, impl, fn, unsafe, typeof, dyn}입니다. 새로운 토큰이 추가될 때 부록을 업데이트하는 것을 잊을 수 있으므로 이 목록이 완전하지 않을 수 있습니다.
복합 M에 대한 FOLLOW의 예시:
- FOLLOW(
$( $d:ident $e:expr )*) = FOLLOW($e:expr) - FOLLOW(
$( $d:ident $e:expr )* $(;)*) = FOLLOW($e:expr) ∩ ANYTOKEN = FOLLOW($e:expr) - FOLLOW(
$( $d:ident $e:expr )* $(;)* $( f |)+) = ANYTOKEN
유효하거나 유효하지 않은 매처의 예시
위의 명세를 바탕으로, 특정 매처가 왜 적법하고 다른 것들은 그렇지 않은지에 대한 논거를 제시할 수 있습니다.
-
($ty:ty < foo ,): 부적법. FIRST(< foo ,) = {<} ⊈ FOLLOW(ty)이기 때문입니다. -
($ty:ty , foo <): 적법. FIRST(, foo <) = {,} ⊆ FOLLOW(ty)이기 때문입니다. -
($pa:pat $pb:pat $ty:ty ,): 부적법. FIRST($pb:pat $ty:ty ,) = {$pb:pat} ⊈ FOLLOW(pat)이고, 또한 FIRST($ty:ty ,) = {$ty:ty} ⊈ FOLLOW(pat)이기 때문입니다. -
( $($a:tt $b:tt)* ; ): 적법. FIRST($b:tt) = {$b:tt} ⊆ FOLLOW(tt) = ANYTOKEN이고, FIRST(;) = {;} 또한 ⊆ ANYTOKEN이기 때문입니다. -
( $($t:tt),* , $(t:tt),* ): 적법. (단, 실제로 이 매크로를 사용하려고 시도하면 확장 과정에서 지역적 모호성 오류가 발생합니다.) -
($ty:ty $(; not sep)* -): 부적법. FIRST($(; not sep)* -) = {;,-}가 FOLLOW(ty)에 없기 때문입니다. -
($($ty:ty)-+): 부적법. 구분자-가 FOLLOW(ty)에 없기 때문입니다. -
($($e:expr)*): 부적법. expr NT들이 FOLLOW(expr NT)에 없기 때문입니다.
영향
러스트는 특별히 독창적인 언어는 아니며, 광범위한 소스에서 유래한 설계 요소들을 가지고 있습니다. 그중 일부는 다음과 같습니다(이후에 제거된 요소 포함):
- SML, OCaml: 대수적 데이터 타입, 패턴 매칭, 타입 추론, 세미콜론을 이용한 구문 분리
- C++: 참조, RAII, 스마트 포인터, 이동 세만틱, 단일화(monomorphization), 메모리 모델
- ML Kit, Cyclone: 리전(region) 기반 메모리 관리
- Haskell (GHC): 타입 클래스, 타입 패밀리
- Newsqueak, Alef, Limbo: 채널, 동시성
- Erlang: message passing, thread failure,
linked thread failure,lightweight concurrency - Swift: 옵셔널 바인딩
- Scheme: 위생적(hygienic) 매크로
- C#: 속성 (attributes)
- Ruby: closure syntax,
block syntax - NIL, Hermes:
typestate - Unicode Annex #31: 식별자 및 패턴 구문
테스트 요약
다음은 레퍼런스 내의 개별 규칙 식별자에 연결된 총 테스트 요약입니다.
용어집
추상 구문 트리 (Abstract syntax tree)
‘추상 구문 트리’ 또는 ‘AST’는 컴파일러가 프로그램을 컴파일할 때 사용하는 프로그램 구조의 중간 표현입니다.
정렬 (Alignment)
값의 정렬은 값이 시작되는 선호되는 주소를 지정합니다. 항상 2의 거듭제곱입니다. 값에 대한 참조는 반드시 정렬되어야 합니다. 자세히.
Application binary interface (ABI)
An application binary interface (ABI) defines how compiled code interacts with other compiled code. With extern blocks and extern fn, ABI strings affect:
- Calling convention: How function arguments are passed, values are returned (e.g., in registers or on the stack), and who is responsible for cleaning up the stack.
- Unwinding: Whether stack unwinding is allowed. For example, the
"C-unwind"ABI allows unwinding across the FFI boundary, while the"C"ABI does not.
항수 (Arity)
항수는 함수나 연산자가 취하는 인자의 개수를 의미합니다. 예를 들어, f(2, 3) 와 g(4, 6) 는 항수가 2이고, h(8, 2, 6) 는 항수가 3입니다. ! 연산자는 항수가 1입니다.
배열 (Array)
배열(array)은 때로 고정 크기 배열 또는 인라인 배열이라고도 하며, 프로그램이 실행 시간에 계산할 수 있는 인덱스로 각각 선택되는 요소들의 컬렉션을 설명하는 값입니다. 메모리의 연속적인 영역을 차지합니다.배열(array)은 때로 고정 크기 배열 또는 인라인 배열이라고도 하며, 프로그램이 실행 시간에 계산할 수 있는 인덱스로 각각 선택되는 요소들의 컬렉션을 설명하는 값입니다. 메모리의 연속적인 영역을 차지합니다.
연관 아이템
연관 아이템은 다른 아이템과 연관된 아이템입니다. 연관 아이템은 구현에 정의되고 트레잇 에 선언됩니다. 함수, 상수, 타입 별칭만 연관될 수 있습니다. 자유 아이템 과 대조됩니다.
블랭킷 구현
타입이 커버되지 않은 상태로 나타나는 모든 구현. impl<T> Foo for T, impl<T> Bar<T> for T, impl<T> Bar<Vec<T>> for T, 그리고 impl<T> Bar<T> for Vec<T> 는 블랭킷 구현으로 간주됩니다. 하지만 impl<T> Bar<Vec<T>> for Vec<T>는 이 impl 에 나타나는 모든 T 인스턴스가 Vec 에 의해 커버되므로 블랭킷 구현이 아닙니다.
바운드
바운드는 타입이나 트레잇에 대한 제약 조건입니다. 예를 들어, 함수가 받는 인자에 바운드가 지정되면 해당 함수에 전달되는 타입은 해당 제약 조건을 준수해야 합니다.
컴비네이터
컴비네이터는 함수와 이전에 정의된 컴비네이터만을 적용하여 인자로부터 결과를 제공하는 고차 함수입니다. 모듈식으로 제어 흐름을 관리하는 데 사용할 수 있습니다.
크레이트
크레이트는 컴파일 및 링크의 단위입니다. 라이브러리나 실행 파일과 같은 다양한 크레이트 종류 가 있습니다. 크레이트는 외부 크레이트라고 하는 다른 라이브러리 크레이트를 링크하고 참조할 수 있습니다. 크레이트는 크레이트 루트라고 하는 이름 없는 루트 모듈에서 시작하는 자체 포함된 모듈 트리를 가집니다. 아이템 은 공개 모듈의 경로 를 포함하여 크레이트 루트에서 공개로 표시하여 다른 크레이트에서 볼 수 있도록 할 수 있습니다. 더 보기.
디스패치
Dispatch is the mechanism to determine which specific version of code is actually run when it involves polymorphism. Two major forms of dispatch are static dispatch and dynamic dispatch. Rust supports dynamic dispatch through the use of trait objects.
동적 크기 타입
동적 크기 타입(DST)은 정적으로 알려진 크기나 정렬이 없는 타입입니다.
엔티티
엔티티 는 소스 프로그램 내에서 어떤 방식으로든, 보통 경로를 통해 참조될 수 있는 언어 구성 요소입니다. 엔티티에는 타입, 아이템, 제네릭 파라미터, 변수 바인딩, 루프 레이블, 라이프타임, 필드, 속성, 린트가 포함됩니다.
표현식
표현식은 값, 상수, 변수, 연산자 및 함수의 조합으로, 부수 효과가 있거나 없을 수 있으며 단일 값으로 평가됩니다.
예를 들어, 2 + (3 * 4) 는 값 14를 반환하는 표현식입니다.
자유 아이템
구현 의 멤버가 아닌 아이템 으로, 자유 함수 또는 자유 상수 와 같은 것입니다. 연관 아이템 과 대조됩니다.
기본 트레잇
기본 트레잇은 기존 타입에 대해 구현을 추가하는 것이 파괴적인 변경이 되는 트레잇입니다. Fn 트레잇과 Sized 가 기본 트레잇입니다.
기본 타입 생성자
기본 타입 생성자는 그 위에 블랭킷 구현 을 구현하는 것이 파괴적인 변경이 되는 타입입니다. &, &mut, Box, Pin 이 기본 타입 생성자입니다.
타입 T 가 로컬 로 간주될 때마다 &T, &mut T, Box<T>, Pin<T> 도 로컬로 간주됩니다. 기본 타입 생성자는 다른 타입을 커버할 수 없습니다. “커버된 타입“이라는 용어가 사용될 때마다 &T, &mut T, Box<T>, Pin<T> 의 T 는 커버된 것으로 간주되지 않습니다.
인해비티드
타입은 생성자가 있어 인스턴스화할 수 있는 경우 인해비티드(inhabited)됩니다. 인해비티드 타입은 해당 타입의 값이 있을 수 있다는 의미에서 “비어 있지” 않습니다. 언인해비티드 의 반대입니다.
고유 구현
트레잇-타입 쌍이 아닌 명목 타입에 적용되는 구현 입니다. 더 보기.
고유 메서드
트레잇 구현이 아닌 고유 구현에 정의된 메서드 입니다.
초기화됨
변수는 값이 할당되고 그 이후로 이동되지 않은 경우 초기화됩니다. 다른 모든 메모리 위치는 초기화되지 않은 것으로 간주됩니다. 안전하지 않은 Rust만이 초기화하지 않고 메모리 위치를 생성할 수 있습니다.
로컬 트레잇
현재 크레이트에서 정의된 trait 입니다. 트레잇 정의는 적용된 타입 인자와 독립적으로 로컬이거나 로컬이 아닐 수 있습니다. trait Foo<T, U> 가 주어졌을 때, T 와 U 에 대해 대체된 타입에 관계없이 Foo 는 항상 로컬입니다.
로컬 타입
현재 크레이트에서 정의된 struct, enum 또는 union 입니다. 이것은 적용된 타입 인자에 영향을 받지 않습니다. struct Foo 는 로컬로 간주되지만 Vec<Foo>는 그렇지 않습니다. LocalType<ForeignType> 은 로컬입니다. 타입 별칭은 지역성에 영향을 주지 않습니다.
모듈
모듈은 0개 이상의 아이템 을 담는 컨테이너입니다. 모듈은 크레이트 루트 또는 루트 모듈이라고 하는 루트의 이름 없는 모듈에서 시작하는 트리로 구성됩니다. 경로 는 다른 모듈의 아이템을 참조하는 데 사용될 수 있으며, 이는 가시성 규칙 에 의해 제한될 수 있습니다. 더 보기
이름
이름 은 엔티티 를 참조하는 식별자 또는 라이프타임 또는 루프 레이블 입니다. 이름 바인딩 은 엔티티 선언이 해당 엔티티와 연관된 식별자 또는 레이블을 도입할 때입니다. 경로, 식별자 및 레이블은 엔티티를 참조하는 데 사용됩니다.
이름 확인
이름 확인(Name resolution) 은 컴파일 타임에 경로, 식별자, 레이블 을 엔티티 선언에 연결하는 프로세스입니다.
네임스페이스 (Namespace)
네임스페이스 는 이름이 참조하는 엔티티 의 종류에 따른 선언된 이름 들의 논리적 그룹화입니다. 네임스페이스를 통해 한 네임스페이스에 있는 이름이 다른 네임스페이스에 있는 동일한 이름과 충돌하지 않도록 할 수 있습니다.
네임스페이스 내에서 이름은 계층 구조로 조직되며, 계층 구조의 각 레벨은 자신만의 명명된 엔티티 컬렉션을 가집니다.
공칭 타입 (Nominal types)
경로를 통해 직접 참조할 수 있는 타입입니다. 구체적으로는 열거형, 구조체, 유니온, 그리고 트레잇 객체 타입 을 의미합니다.
Dyn 호환 트레잇 (Dyn-compatible traits)
트레잇 객체 타입 (dyn Trait)에서 사용될 수 있는 트레잇 입니다. 특정한 규칙 을 따르는 트레잇만이 dyn 호환 됩니다.
이들은 이전에 객체 안전한(object safe) 트레잇으로 알려져 있었습니다.
경로 (Path)
경로 는 현재 스코프나 다른 레벨의 네임스페이스 계층 구조에 있는 엔티티 를 참조하기 위해 사용되는 하나 이상의 경로 세그먼트 시퀀스입니다.
프렐류드 (Prelude)
프렐류드(또는 러스트 프렐류드)는 모든 크레이트의 모든 모듈로 임포트되는 아이템들(주로 트레잇)의 작은 모음입니다. 프렐류드에 포함된 트레잇들은 어디에서나 사용됩니다.
스코프
스코프 는 명명된 엔티티 가 해당 이름으로 참조될 수 있는 소스 텍스트의 영역입니다.
검사 대상 (Scrutinee)
검사 대상(scrutinee)은 match 표현식이나 유사한 패턴 매칭 구문에서 매칭의 대상이 되는 표현식입니다. 예를 들어, match x { A => 1, B => 2 } 에서 표현식 x 가 검사 대상입니다.
크기 (Size)
값의 크기에는 두 가지 정의가 있습니다.
첫 번째는 해당 값을 저장하기 위해 얼마나 많은 메모리가 할당되어야 하는가입니다.
두 번째는 해당 아이템 타입을 가진 배열에서 연속된 요소들 사이의 바이트 단위 오프셋입니다.
이는 0을 포함하여 정렬의 배수입니다. 크기는 컴파일러 버전(새로운 최적화가 도입됨에 따라)이나 타겟 플랫폼(usize 가 플랫폼마다 다른 것과 마찬가지)에 따라 달라질 수 있습니다.
자세히.
슬라이스 (Slice)
슬라이스는 연속된 시퀀스에 대한 동적 크기 뷰(view)이며, [T] 로 작성됩니다.
주로 가변 또는 공유 형태의 차용된 형태로 나타납니다. 공유 슬라이스 타입은 &[T] 이며, 가변 슬라이스 타입은 &mut [T] 입니다. 여기서 T 는 요소의 타입을 나타냅니다.
구문 (Statement)
구문은 컴퓨터에 동작을 수행하도록 명령하는 프로그래밍 언어의 가장 작은 독립 요소입니다.
문자열 리터럴 (String literal)
문자열 리터럴은 최종 바이너리에 직접 저장되는 문자열이며, 따라서 'static 기간 동안 유효합니다.
그 타입은 'static 기간의 차용된 문자열 슬라이스인 &'static str 입니다.
문자열 슬라이스 (String slice)
문자열 슬라이스는 러스트에서 가장 기본적인 문자열 타입으로, str 로 작성됩니다. 주로 가변 또는 공유 형태의 차용된 형태로 나타납니다. 공유 문자열 슬라이스 타입은 &str 이며, 가변 문자열 슬라이스 타입은 &mut str 입니다.
문자열 슬라이스는 항상 유효한 UTF-8입니다.
트레잇 (Trait)
트레잇은 타입이 제공해야 하는 기능을 설명하는 데 사용되는 언어 아이템입니다. 이를 통해 타입은 자신의 동작에 대해 특정한 약속을 할 수 있습니다.
제네릭 함수와 제네릭 구조체는 트레잇을 사용하여 받아들이는 타입을 제한하거나 바인딩(bound)할 수 있습니다.
터보피쉬 (Turbofish)
표현식에서 제네릭 파라미터를 포함하는 경로는 여는 괄호 앞에 :: 를 붙여야 합니다. 제네릭을 위한 화살괄호와 결합하면 ::<> 와 같이 물고기처럼 보입니다. 이 때문에 이 구문은 구어체로 터보피쉬 구문이라고 불립니다.
예:
#![allow(unused)]
fn main() {
let ok_num = Ok::<_, ()>(5);
let vec = [1, 2, 3].iter().map(|n| n * 2).collect::<Vec<_>>();
}
이 :: 접두사는 쉼표로 구분된 목록에서 여러 비교 연산이 포함된 제네릭 경로의 모호성을 해결하기 위해 필요합니다. 접두사가 없을 때 모호해질 수 있는 예시는 the bastion of the turbofish 를 참조하십시오.
비피복 타입 (Uncovered type)
다른 타입의 인자로 나타나지 않는 타입입니다. 예를 들어, T 는 피복되지 않았지만(uncovered), Vec<T> 에서의 T 는 피복되었습니다(covered). 이는 타입 인자에서만 의미가 있습니다.
정의되지 않은 동작 (Undefined behavior)
명시되지 않은 컴파일 타임 또는 런타임 동작입니다. 이는 프로세스 종료나 손상, 부적절하거나 부정확하거나 의도하지 않은 계산, 또는 플랫폼별 결과 등을 초래할 수 있으나 이에 국한되지 않습니다. 자세히.
비거주 (Uninhabited)
생성자가 없어서 인스턴스화할 수 없는 타입입니다. 비거주 타입은 해당 타입의 값이 없다는 의미에서 “비어 있음“을 뜻합니다. 비거주 타입의 대표적인 예로는 never 타입 ! 또는 변형이 없는 열거형인 enum Never { } 가 있습니다. 거주(Inhabited) 의 반대말입니다.